Engineering Notes

孟斌的小站

技术博客与学习记录

共 608 篇文章 标签与分类索引已启用

《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞

课程目标

  • 理解 Solidity 编译器的存储布局机制
  • 学会识别 存储槽冲突、ABI 混淆攻击
  • 掌握 selfdestruct 等低级指令的风险
  • 通过 Foundry 测试模拟攻击与验证

1、存储槽冲突(Storage Slot Collision)

Solidity 使用 32 字节为一个存储槽(storage slot)。在继承或代理合约模式下,如果新旧合约的状态变量定义不一致,就可能发生槽冲突,导致关键数据被覆盖。

继续阅读

《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱

1、可升级的必要性与问题

1. 区块链合约不可变的特性

  • 在区块链上部署的合约代码是永久存储的,不可直接更改或删除。
  • 这种不可变性保障了去中心化和安全性,但也意味着:
    • 一旦有 bug,无法直接修改。
    • 一旦需要新增功能,只能重新部署一个新版本。

2. 部署新合约迁移 vs 升级逻辑合约

  • 部署新合约迁移
    • 需要将旧合约中的状态数据(余额、映射等)迁移到新合约。
    • 迁移过程复杂、易出错、消耗大量 gas。
    • 用户需要更新交互地址,容易引起混乱。
  • 升级逻辑合约
    • 通过代理模式保留原有存储,替换逻辑实现。
    • 用户交互地址不变,数据原地保留。
    • 只需在升级时注意存储布局一致性。

2、可升级合约的核心思想

  • 问题:合约一旦部署,代码无法更改。
  • 解决方案:将合约分为 代理合约(Proxy)逻辑合约(Implementation)
    • 代理合约:存储状态变量,转发调用给逻辑合约。
    • 逻辑合约:包含可执行代码。
  • 关键技术delegatecall,在代理合约中使用 delegatecall 调用逻辑合约的函数,使得代码在代理的存储上下文中执行。

3、代理模式的工作原理

1. delegatecall 复习

(bool success, bytes memory data) = implementation.delegatecall(msg.data);
  • delegatecall 会在当前合约的存储和上下文中执行目标合约的代码。
  • 状态变量读写会影响代理合约,而不是逻辑合约。

2. 存储布局一致性

  • 代理合约和逻辑合约必须保持相同的状态变量声明顺序和类型,否则会出现数据错位。

4、常见可升级合约模式

1. 透明代理(Transparent Proxy)

  • EIP-1967 标准。
  • 普通用户调用逻辑合约函数;管理员调用代理的管理函数(升级逻辑合约地址)。
  • 优点:简单、被广泛支持(OpenZeppelin Proxy)。
  • 缺点:管理逻辑和业务逻辑混在同一个合约中,稍显冗余。

2. UUPS(Universal Upgradeable Proxy Standard)

  • EIP-1822 标准。
  • 升级逻辑放在逻辑合约自身,由 upgradeTo 函数完成。
  • 优点:代理合约更轻量,升级逻辑可定制。
  • 缺点:升级安全完全依赖逻辑合约实现,容易被错误实现破坏。

3. Beacon Proxy

  • 使用一个 Beacon 合约统一存储逻辑合约地址,多个代理共享升级源。
  • 适合多实例共享逻辑的场景。

5、可升级合约的安全陷阱

| 风险点 | 说明 | 解决方案 | | :-------------------- | :------------------------------------------------- | :-------------------------------------------------- | | 存储布局冲突 | 升级后逻辑合约的变量顺序、类型不一致,导致数据错位 | 遵循固定的变量追加规则,避免删除或更改类型 | | 初始化漏洞 | 新逻辑合约的构造函数不会被代理调用 | 使用 initializer 修饰的初始化函数,防止重复初始化 | | delegatecall 风险 | 调用外部不可信合约可能破坏存储 | 严格控制升级权限,禁止不可信代码执行 delegatecall | | 权限丢失 | 升级过程中可能被替换成恶意逻辑 | 使用多签或 Timelock 控制升级 |

继续阅读

《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”

1. 三种低级调用方式对比

| 调用方式 | 是否切换上下文(storage/msg.sender/msg.value) | 是否能改状态 | 特点与用途 | |:-------------- |:------------------------------------- |:------ |:----------------------- | | call | ✅ 切换到被调用合约 | ✅ | 最通用的外部调用,可带 ETH,可调用任意函数 | | delegatecall | ❌ 保持当前合约上下文 | ✅ | 代理模式核心,让当前合约执行别人的代码 | | staticcall | ✅ 切换到被调用合约 | ❌ | 安全读取外部数据,不改状态 |

继续阅读