Crypto OS
职业 Track

T1 · EVM Engineer

从「合约能跑」走到「我知道它在什么条件下会坏,坏成什么样」。

面向以 EVM 为主战场的合约工程师

本页里的「T1」指这条 Track。课程章节一律写成「第 T8 章」并带链接,两者不是一回事——章节 T1 讲的是链上数据究竟是什么。

这条 Track 适合谁

不看岗位名,看你现在卡在哪。下面四种状态,命中一条就适合进来。

你写得出合约,但说不清它为什么安全。 部署过测试网,测试全绿,覆盖率好看。有人问「你怎么知道它是安全的」,你能给出的最强回答是「测试都过了」。你自己也知道这句话不够,但不知道还能补什么。

你读不懂主网上的真实代码。 打开一份线上协议的核心合约,里面是代理、内联汇编、位压缩、自定义错误。你分不清哪些是必要的工程取舍,哪些只是作者在炫技,于是也不敢抄。

你的 Gas 优化靠背口诀。 记得「用 calldatamemory 便宜」「变量要打包」,但给你一份合约,你算不出它的 storage layout,也说不出把哪两个变量换个位置能省多少。

你在面试里答得出定义,答不出后果。delegatecall 是在调用方的存储上下文里执行被调方的代码」——这句话你会背。再追问「所以它会怎么出事」,你只能说「会有存储冲突」,举不出一个具体的例子。

这条 Track 的分界线只有一句话:你交付的合约,附带的是「我测过了」,还是「我知道它在什么条件下会坏,以及那时候会坏成什么样」。

如果你还没在任何测试网上部署过东西,先别进来。回去把 Part B 第二阶段做完,这条 Track 默认你已经有一个能跑的仓库。

前置

Part B 全 36 章读完当然最好。但如果你想现在就开始,知识图谱里通向这条 Track 的最短路径是这一串:

  1. 第 6 章
  2. 第 T1–T4 章
  3. 第 T5 章
  4. 第 9 章
  5. 第 T7 章
  6. 第 T8 章
  7. 第 T9 章
  8. 第 T12 章
  9. 第 T28 章
依赖关系取自 graph.ts,不是按阶段顺序排的
必须先读图谱里的理由
第 6 章 · 一次链上交易发生了什么第 T1 章的直接前置。不先把「一次交易」当成一段状态变更来理解,后面所有关于顺序的讨论都接不上
第 T1 章第 T4 章状态、签名、交易生命周期、RPC。这四章是 Part B 里唯一没有捷径的部分
第 T5 章 · EVM Account Model第 T7 章的直接前置。Storage slot 是这条 Track 从头到尾的计价单位
第 9 章 · Smart Contract 改变了什么图谱里它同样是第 T7 章的直接前置,也是 Part A 那一侧唯一的硬依赖
第 T7 章第 T8 章第 T9 章语言、EVM 成本模型、标准。这条 Track 的正题从这里开始
第 T12 章 · Testing & Deployment图谱里它依赖第 T7、T9 章。Fork 测试是这条 Track 的日常,不是某一章的练习
第 T28 章 · Smart Contract Security图谱里它依赖第 T8、T9、T12 章。三条都补齐了,你才读得懂它为什么强调顺序而不是语法

你会学什么

  1. Solidity
  2. Foundry
  3. Hardhat
  4. EVM Internals
  5. DeFi
  6. Security

六个 topic 不是六门课,是四组问题。

一、语言与工具链:把「能编译」换成「能被验证」

Solidity 的深水区从继承的线性化顺序开始,一路到自定义错误的 revert 数据、unchecked 的正确使用范围、以及短暂存储适合放什么。这些都不是语法题,而是「这一行会让谁付多少钱」的问题。

两套主流工具链不是二选一。一套以合约语言写测试、把 Fuzzing 和不变量当成一等公民;另一套以脚本语言做部署、任务编排和前端集成。现实里成熟仓库常常两套都在跑:测试与 Fuzzing 在前者,部署与集成在后者。这条 Track 要求你两套都能从空目录搭起来,并说得清这个仓库为什么这样分工。

二、EVM Internals:把 Gas 从玄学变成算术

要能不部署就写出一份合约的 storage layout:槽位怎么分配、什么条件下会被打包进同一个槽、mapping 与动态数组的槽位怎么算。在此基础上,calldatamemorystorage 的成本差就不再需要背,而是能算。

然后是代理。delegatecall 改写的是「这是谁的存储」,几种主流代理布局的差别,本质上都是在回答「怎样让实现合约的变量不踩到代理自己的变量」。

自检方式很具体:给你一份别人的合约,你能指出哪两个变量应该换位置,以及换完之后省下的是一次冷写还是一次热写。

三、DeFi:你的合约要和谁组合

不需要做到 Track T4 的深度,但要能读懂主流协议的核心合约,并且知道它们会怎样和你的代码互相伤害:

  • 不返回 bool 的转账、带税代币、余额会自己变的代币、小数位不是 18 的代币
  • 精度与取整——取整方向必须永远对协议有利,这条在第 T28 章讲过
  • 预言机接口的失效模式:数据过期、偏离超阈值、返回零
  • 带回调的代币标准会把重入带进「我没转过 ETH」的函数里

四、Security:把安全做成会自动运行的东西

第 T30 章的威胁模型四问和不变量三类,在这条 Track 里从「读过」变成「每个项目都写一遍」。配套的是有状态 Fuzzing、当成过滤器而不是判决书的静态分析、以及权限设计:多签、时间锁、暂停开关各自防的是什么。

毕业作品

一套经过 Fuzzing 与 Fork 测试的主网合约。

毕业项目的区别要先说清楚:毕业项目是 Core 的收尾,要求横向完整——合约、前端、后端、索引、部署一条龙。这条 Track 的作品只要合约这一层,但纵向要深得多。你可以拿毕业项目的合约层当起点,但按下面五条重新验收一遍。

别人能不能跑起来。 clone 之后,一条命令装依赖,一条命令跑完全部测试。README 写明需要哪些环境变量、Fork 用的 RPC 从哪来、以及在没有归档节点时怎么退化运行。

跑不起来的仓库在招聘和合作里等于不存在,这一条永远排第一。

Fuzzing 真的在跑,而且真的红过。 至少五条不变量,三类各有覆盖。仓库里要留下一次「故意破坏一条、它被抓到」的记录:那条最短失败序列,连同当时的随机种子。

一个永远绿的检查和一个不存在的检查效果相同,但前者会给你虚假的安心。

每一个外部依赖都有 Fork 测试。 凡是你的合约会调用的外部地址,都要有一条在 Fork 上跑的用例。写清楚你 Fork 的是哪条链、哪个高度,以及为什么选这个高度。

Gas 有基线,涨了能解释。 关键函数的 Gas 快照进版本库。任何一次改动让某个数字变大,你要能说出多出来的是哪一次存储写入或哪一次外部调用。

有威胁模型和升级路径。 四问全部回答,第四问「你明确不防什么」至少写两条。如果合约可升级,写清楚谁能升级、多久生效、以及怎么停。

主网部署这一步:用你自己的钱,小额,而且只部署已经跑完前四条的版本。 只交测试网部署加一份「为什么现在还不该上主网」的说明,在评审里不扣分——那反而说明你有判断力。

怎样判断自己达标了

常见误区

「覆盖率 100% 说明测够了」

覆盖率衡量的是每一行被执行过,不是每一种调用顺序被执行过。真实漏洞几乎都藏在序列里:几个函数单独看都对,特定顺序下舍入累积或状态错位就出事。

正确做法:把覆盖率当下限门槛,信心来自有状态 Fuzzing 和不变量。

「小类型更省 Gas」

uint256 换成 uint8 不一定省钱。只有当几个小变量真的被打包进同一个槽时才省;没打包成功的时候,编译器为了截断和掩码反而要多做运算,比原来更贵。

正确做法:先量,再改,再量。优化前后各留一份快照,省下来的数字要能对应到具体的槽位变化。

「可升级 = 更安全」

可升级只是把风险换了个位置:从「代码可能写错」换成「升级权限可能被滥用或被盗」。后者的爆炸半径大得多,而且一次到位——一次恶意升级可以让所有不变量同时失效。

正确做法:先问这个合约到底需不需要可升级。确实需要的,把升级权限本身当成协议里最高危的资产来设计:多签、时间锁、以及一个公开可查的升级流程。

「审计过了就安全」

审计的范围就是它的能力边界,而范围是你和审计方一起定的。你没提供威胁模型,范围默认只剩「代码实现的正确性」,经济假设整个不在里面。第 T30 章那个「17 个发现全修完仍被搬空」的例子讲的就是这件事。

正确做法:审计之前先交威胁模型,审计之后把每一条发现转成一条不变量。前者决定他们查什么,后者决定这次审计的成果会不会随着下一次提交流失。

接下来

本页目录