智能合约

智能合约:「由代码执行契约」的构想

把约定好的内容交由程序而非人或中介来自动执行,智能合约由此把区块链从「价值的记录」提升为「驱动价值的应用基础」。

什么是智能合约

智能合约是部署在区块链上、当预先设定的条件满足时自动执行的程序。把「A 向 B 转账后,C 向 A 发放代币」这样的规则写成代码,它便会不掺入任何人的裁量、照此可靠执行。如果说纸质合同是「期望被遵守」,那么智能合约就是以「无从违背」的形式强制执行约定的机制。

这一概念早在 1990 年代就由法学者尼克·萨博提出,而让它成真的是 2015 年问世的以太坊。它被设计为不仅能记录转账、还能执行任意程序的「世界级分布式计算机」。

运行原理:EVM·Gas·状态转移

智能合约运行在各节点自备的虚拟机(以太坊为 EVM:以太坊虚拟机)之上。交易调用函数时,所有节点必须执行同一段代码并得到同一结果。这种确定性执行是必需的。随机数、当前时间、外部通信这类「结果会变化的操作」之所以不能直接使用,正是这个原因。

执行是有成本的。每一步都以 Gas 为单位收取手续费,用加密资产支付。Gas 既为消耗的资源计价,也用于防止用无限循环令网络瘫痪的攻击。Gas 耗尽则执行中止、状态回滚,但已消耗的 Gas 不予退还。大致的消耗量如下(为概算数值)。

操作大致 Gas 消耗量备注
简单的 ETH 转账21,000所有交易的基本成本
新建存储写入(SSTORE,0→非0)20,000新建状态的操作成本尤高
既有存储更新(SSTORE,非0→非0)2,900槽位已「热」时的大致值
存储读取(SLOAD,冷)2,100仅首次访问;此后较便宜

要点在于:改写状态的操作成本更高,因为这会增加所有节点须持续保存的数据量。相对地,不改变状态的只读 view 函数无需经过交易、只需本地查询节点即可,因此不消耗 Gas。

合约的执行,就是区块链的状态转移本身。「谁持有多少何种代币」之类的状态,经由交易确定性地迁移到下一状态,并在所有节点达成一致(参见分布式共识)后确定。

从部署到执行的流程

智能合约要真正运行起来,需要经过几个固定的步骤。

  1. 编写:用 Solidity(最普及的语言)或 Vyper 等编写逻辑。
  2. 编译:编译器把源代码转换为 EVM 可解释的字节码,以及供外部调用函数所用的接口定义 ABI
  3. 发送部署交易:不指定接收地址,把字节码放入 data 字段发送交易。
  4. 确定地址:新合约地址由发送者地址与当时的 nonce 经 keccak256 确定性计算得出(若用 CREATE2 操作码,也可用任意 salt 事先确定地址)。
  5. 执行构造函数:部署时只运行一次的初始化代码,写入初始状态。
  6. 接受调用:此后,每当有交易携带 ABI 编码的 calldata 发往该地址,对应函数便会执行。

Solidity 代码示例:最小化的存取款合约

来看一个实际的例子:下面这份合约允许任何人存入 ETH,并只能在自己余额范围内取出。

contract Vault { mapping(address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "insufficient balance"); balances[msg.sender] -= amount; (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed"); } }

  • mapping(address => uint256) balances:保存各地址余额的状态变量。写入其中的值,就是全体节点共享的区块链状态本身。
  • external payable:表示该函数可从合约外部调用,且能同时接收 ETH 的函数修饰符。
  • require(条件, "消息"):条件不满足时立即回滚整个调用,是输入校验与权限控制的基本手段。
  • balances[...] -= amount 放在外部调用(call之前执行至关重要。若顺序颠倒,就会留下下一节所讲重入攻击的空隙(即检查-生效-交互(Checks-Effects-Interactions)模式)。

代表性应用场景

  • 代币(ERC-20 / NFT):一个只有「余额表」和「转账函数」的简单合约,就成了 ERC-20 标准下的代币。表示独一无二数字资产的 NFT(ERC-721),也不过是记录所有者的合约。得益于统一标准,各钱包和交易所都能一视同仁地处理它们。
  • DeFi(去中心化金融):无需中央金融机构,合约自动居间撮合交易。DEX 用流动性池让人人皆可兑换代币,借贷则可抵押资产借出加密货币。所有规则都以代码公开并自动执行。
  • 托管·自动结算:结合多重签名或条件转账,可实现无需中介的托管。

漏洞、真实事件与防御的教训

「代码会被可靠执行」,反过来说就是漏洞也会被可靠执行。正因合约公开且不可逆,漏洞才会直接导致致命损失。

  • 重入攻击:利用合约向外部发起调用的空隙,在状态更新之前递归调用同一函数以抽走资金。2016 年的 The DAO 事件中约 360 万 ETH 外流,导致以太坊硬分叉、分裂为以太坊与以太坊经典。防御手段是「检查-生效-交互」模式,以及 ReentrancyGuard 之类的防重入修饰符。
  • 整数溢出:数值超出可表示上限而回绕、导致余额计算崩溃的经典漏洞。Solidity 0.8 以后编译器会自动检查算术运算,但更早的版本需显式使用 SafeMath 库,遗漏引入曾造成实际损失。
  • 权限控制不足:「谁可以调用该函数」检查缺失。2021 年的 Poly Network 黑客事件中,跨链管理合约的权限验证存在缺陷,攻击者取得了本不应拥有的管理员级权限,从多条链上共卷走逾 6 亿美元(大部分事后被归还)。对策在于给需要权限的函数彻底加上 onlyOwner 之类的修饰符。
  • 不可升级性:已部署的代码原则上无法改写。为修正会使用代理模式,但这本身又带来新的复杂性和权限集中的风险。
  • 预言机问题:因执行是确定性的,合约无法自行获取外部现实世界的信息。若充当桥梁的预言机提供了错误数据,写得再正确的合约也会误动作。

防御的要点,是使用经过审计、久经考验的代码,并用多重签名等分散权限管理。合约钱包(如 Safe)正是典型代表。

审计与测试的实务

合约所经手的价值越大,部署前经过的验证阶段就越多,这已是业界的标准做法。

  • 静态分析:用 Slither、Mythril 等工具从代码中自动检出重入、溢出等已知漏洞模式,是成本低廉的第一道筛查。
  • 单元测试与模糊测试:用 Foundry、Hardhat 编写测试套件、验证边界值;用随机输入检验「应始终成立的性质」是否被打破的 invariant testing 也被广泛使用。
  • 第三方审计:由 OpenZeppelin、Trail of Bits 等专业公司进行代码审查。但通过审计并不等于零漏洞的证明,审计后若修改代码,该保证便随之失效。
  • 形式化验证:以数学方法证明代码与规范一致的手法。Certora 等工具主要用于资产规模较大的 DeFi 协议。
  • 漏洞赏金:Immunefi 等平台向发现漏洞者支付赏金,制造让白帽先于攻击者发现漏洞的经济激励。
  • 分阶段部署:先在测试网充分验证,再部署到主网。

在 L2·Rollup 上的执行

以太坊主网(L1)吞吐量有限,需求集中时 Gas 费便会飙升。把大部分执行移到 L1 之外(L2)的 Rollup,是当前主流的应对方案。在兼容 EVM 的 Rollup 上,字节码几乎与 L1 相同地运行。

  • Optimistic Rollup:先把提交的交易当作「正确」立即生效,若有疑似欺诈,可在挑战期(通常约一周)内提交欺诈证明(fraud proof)推翻结果。Arbitrum、Optimism 采用此方案。
  • ZK Rollup:以密码学上的可验证证明(validity proof,如 zk-SNARK)立即证明批次的状态转移正确无误。L1 只需验证证明,无需像 Optimistic Rollup 那样等待挑战期。zkSync、Starknet 等在此方向上相互竞争。
  • 共同的设计思路:两种方案都在链下执行,只把压缩后的数据或证明提交到 L1,从而大幅削减 Gas 费。最终的安全性仍依赖 L1 的分布式共识

与传统合同的比较

角度传统合同智能合约
执行主体当事人自发履行,或法院·仲裁机构条件成立的同时由程序自动执行
修改·解除经当事人同意可修改或解除合同部署后原则上不可变(需代理模式等技巧)
中介方律师、托管方、金融机构等可能介入无需中介,由代码承担判定与执行
纠纷解决通过协商、诉讼、仲裁解决解释空间小,但漏洞往往致命
透明度仅当事人之间掌握内容代码与历史原则上公开、任何人可验证
执行成本律师费、手续费等仅需 Gas 费即可完成

智能合约试图把契约的「解释」与「执行」从人的裁量中剥离出来,交给代码这一单一的真相。这份严谨既是优势,反过来也意味着代码的质量就是可信度的上限。审计、测试、分阶段部署这些看似朴素的工序,正是让这项技术足以付诸实用的关键。

返回首页