返回 SC 笔记
SC Day 17

Solidity - library + using for + assembly 初探 + 低级调用(call/delegatecall/staticcall)

### 1. Library:可复用的代码库

2026-04-17
第一阶段:基础构建
soliditylibraryassemblycalldelegatecalllow-level

日期: 2026-04-17 方向: Solidity 阶段: 第一阶段:基础构建 标签: #solidity #library #assembly #call #delegatecall #low-level


今日目标

类型内容
学习掌握 library 的两种链接模式、inline assembly 基础、三种低级调用的区别和安全性
实操实现 SafeMath 库、用 call 发送 ETH、用 delegatecall 实现代理模式
产出完整的库代码 + 低级调用示例 + assembly 操作 + 安全分析

核心概念

1. Library:可复用的代码库

Solidity 的 library 是一种特殊的合约,它不能有状态变量,不能接收 ETH,不能被销毁。Library 有两种使用模式。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title 数学工具库
/// @notice 所有函数都是 pure/view,不修改状态
library MathLib {
    /// @notice 安全的除法(Solidity 0.8+ 已内置溢出检查,但不检查除零)
    function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "Division by zero");
        return a / b;
    }

    /// @notice 计算百分比(basis points, 1 bp = 0.01%)
    function percentageBps(uint256 amount, uint256 bps) internal pure returns (uint256) {
        return (amount * bps) / 10000;
    }

    /// @notice 平方根(Babylonian 方法)
    function sqrt(uint256 x) internal pure returns (uint256 y) {
        if (x == 0) return 0;
        y = x;
        uint256 z = (x + 1) / 2;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }

    /// @notice 最小值
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /// @notice 最大值
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }
}

/// @title 地址工具库
library AddressLib {
    /// @notice 检查地址是否为合约
    function isContract(address account) internal view returns (bool) {
        return account.code.length > 0;
    }

    /// @notice 安全发送 ETH
    function sendETH(address payable to, uint256 amount) internal {
        require(address(this).balance >= amount, "Insufficient balance");
        (bool success, ) = to.call{value: amount}("");
        require(success, "ETH transfer failed");
    }
}

2. using for:把库函数附加到类型上

contract TokenSale {
    // using for 语法:让 uint256 类型可以直接调用 MathLib 的函数
    using MathLib for uint256;
    using AddressLib for address;
    using AddressLib for address payable;

    uint256 public price = 100; // 100 wei per token
    uint256 public platformFeeBps = 250; // 2.5% 平台手续费

    function calculateCost(uint256 amount) public view returns (uint256 cost, uint256 fee) {
        cost = amount * price;
        // 直接在 uint256 上调用 MathLib.percentageBps
        // 等同于 MathLib.percentageBps(cost, platformFeeBps)
        fee = cost.percentageBps(platformFeeBps);
    }

    function buy(uint256 amount) external payable {
        (uint256 cost, uint256 fee) = calculateCost(amount);
        require(msg.value >= cost, "Insufficient payment");

        // 使用 AddressLib 检查
        require(!msg.sender.isContract(), "No contracts allowed");

        // 发送手续费
        payable(address(0xFee)).sendETH(fee);
    }

    function sqrtPrice() public view returns (uint256) {
        return price.sqrt(); // MathLib.sqrt(price)
    }
}

3. 三种低级调用:call / delegatecall / staticcall

这三种调用是 Solidity 与外部合约交互的底层机制。理解它们的区别对安全至关重要。

contract LowLevelCalls {
    uint256 public value;
    address public lastCaller;

    // ====== call:最常用的外部调用 ======
    // 特点:在目标合约的上下文中执行
    // msg.sender = 调用方(本合约)
    // 存储上下文 = 目标合约

    function callExample(address target, uint256 amount) external {
        // 调用目标合约的 setValue 函数
        (bool success, bytes memory returnData) = target.call(
            abi.encodeWithSignature("setValue(uint256)", amount)
        );
        require(success, "Call failed");

        // 带 ETH 的 call
        (bool sent, ) = target.call{value: 1 ether}("");
        require(sent, "ETH send failed");

        // 带 Gas 限制的 call
        (bool ok, bytes memory data) = target.call{gas: 50000}(
            abi.encodeWithSignature("expensiveFunction()")
        );
    }

    // ====== delegatecall:代理模式的核心 ======
    // 特点:在调用方(本合约)的上下文中执行目标合约的代码
    // msg.sender = 保持不变(原始调用者)
    // 存储上下文 = 本合约(不是目标合约!)
    // 极度危险:目标合约可以修改本合约的存储

    function delegateCallExample(address implementation) external {
        (bool success, bytes memory data) = implementation.delegatecall(
            abi.encodeWithSignature("setValue(uint256)", 42)
        );
        require(success, "Delegatecall failed");
        // 注意:如果 implementation 修改了 slot 0 的存储,
        // 那么本合约的 value(slot 0)会被修改!
    }

    // ====== staticcall:只读调用 ======
    // 特点:不允许修改状态(view/pure)
    // 如果目标函数试图修改状态,调用会 revert

    function staticCallExample(address target) external view returns (uint256) {
        (bool success, bytes memory data) = target.staticcall(
            abi.encodeWithSignature("getValue()")
        );
        require(success, "Staticcall failed");
        return abi.decode(data, (uint256));
    }
}

三种调用对比表

┌─────────────────┬─────────────┬──────────────┬──────────────┐
│     特性         │    call     │ delegatecall │  staticcall  │
├─────────────────┼─────────────┼──────────────┼──────────────┤
│ 代码来源         │ 目标合约     │ 目标合约      │ 目标合约      │
│ 存储上下文       │ 目标合约     │ 调用方        │ 目标合约      │
│ msg.sender      │ 调用方       │ 原始调用者    │ 调用方        │
│ msg.value       │ 可指定       │ 保持原值      │ 0            │
│ 可修改状态       │ 是           │ 是(调用方的)  │ 否            │
│ 可发送 ETH      │ 是           │ 否            │ 否            │
│ 主要用途         │ 外部调用     │ 代理/升级模式  │ 只读查询      │
│ 安全风险         │ 重入         │ 存储冲突      │ 最安全        │
└─────────────────┴─────────────┴──────────────┴──────────────┘

4. abi.encode 系列函数

contract ABIEncoding {
    // ====== abi.encode:标准 ABI 编码(带填充到32字节)======
    function encodeExample() public pure returns (bytes memory) {
        return abi.encode(uint256(1), address(0x123), "hello");
        // 每个参数填充为 32 字节
    }

    // ====== abi.encodePacked:紧凑编码(不填充)======
    function encodePackedExample() public pure returns (bytes memory) {
        return abi.encodePacked(uint8(1), address(0x123), "hello");
        // 参数紧密排列,不填充
        // 注意:不同参数可能产生相同编码(碰撞风险)
    }

    // ====== abi.encodeWithSignature:编码函数调用 ======
    function encodeCallExample() public pure returns (bytes memory) {
        return abi.encodeWithSignature("transfer(address,uint256)",
            0x1234567890AbCdEf1234567890abCdEf12345678,
            1000
        );
        // = bytes4(keccak256("transfer(address,uint256)")) + abi.encode(args)
    }

    // ====== abi.encodeWithSelector:用 selector 编码 ======
    function encodeSelectorExample() public pure returns (bytes memory) {
        return abi.encodeWithSelector(
            bytes4(keccak256("transfer(address,uint256)")),
            0x1234567890AbCdEf1234567890abCdEf12345678,
            1000
        );
    }

    // ====== abi.decode:解码返回数据 ======
    function decodeExample(bytes memory data) public pure returns (uint256, address) {
        return abi.decode(data, (uint256, address));
    }
}

5. Inline Assembly 初探

Solidity 中的 inline assembly 使用 Yul 语言,可以直接操作 EVM。在需要极致 Gas 优化时使用。

contract AssemblyBasics {
    uint256 private storedValue;

    // ====== 基础操作 ======

    /// @notice 用 assembly 读取存储
    function getValueAssembly() external view returns (uint256 result) {
        assembly {
            // sload:从存储槽读取
            // storedValue 在 slot 0
            result := sload(0)
        }
    }

    /// @notice 用 assembly 写入存储
    function setValueAssembly(uint256 newValue) external {
        assembly {
            // sstore:写入存储槽
            sstore(0, newValue)
        }
    }

    /// @notice 获取调用者地址
    function getCallerAssembly() external view returns (address result) {
        assembly {
            result := caller() // 等同于 msg.sender
        }
    }

    /// @notice 获取当前合约余额
    function getBalanceAssembly() external view returns (uint256 result) {
        assembly {
            result := selfbalance() // 等同于 address(this).balance
        }
    }

    /// @notice 高效的 keccak256
    function hashAssembly(uint256 a, uint256 b) external pure returns (bytes32 result) {
        assembly {
            // 将数据写入内存
            mstore(0x00, a)
            mstore(0x20, b)
            // 对 64 字节数据做 keccak256
            result := keccak256(0x00, 0x40)
        }
    }

    // ====== 实用场景:高效的地址检查 ======

    /// @notice 检查地址是否为零地址(比 require(addr != address(0)) 更省 Gas)
    function isZeroAddress(address addr) external pure returns (bool result) {
        assembly {
            result := iszero(addr)
        }
    }

    // ====== 实用场景:自定义 revert 消息 ======

    /// @notice 用 assembly 实现高效的 require
    function efficientRequire(bool condition) external pure {
        assembly {
            if iszero(condition) {
                // 存储 Error(string) selector
                mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                mstore(0x04, 0x20) // 字符串偏移
                mstore(0x24, 0x0e) // 字符串长度 (14 bytes)
                mstore(0x44, "Check failed!") // 错误消息
                revert(0x00, 0x64)
            }
        }
    }
}

代码实战:ETH 发送的三种方式 + 代理合约模式

ETH 发送对比

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ETHSender {
    // ====== 方法1:transfer(不推荐)======
    // - 固定 2300 gas
    // - 失败自动 revert
    // - 问题:2300 gas 可能不够接收方合约执行逻辑
    function sendViaTransfer(address payable to) external payable {
        to.transfer(msg.value);
    }

    // ====== 方法2:send(不推荐)======
    // - 固定 2300 gas
    // - 失败返回 false(不自动 revert)
    // - 问题:同 transfer,且容易忘记检查返回值
    function sendViaSend(address payable to) external payable {
        bool success = to.send(msg.value);
        require(success, "Send failed");
    }

    // ====== 方法3:call(推荐)======
    // - 转发所有可用 gas(可指定)
    // - 失败返回 (false, data)
    // - 最灵活,但需注意重入攻击
    function sendViaCall(address payable to) external payable {
        (bool success, ) = to.call{value: msg.value}("");
        require(success, "Call failed");
    }

    receive() external payable {}
}

代理合约(delegatecall 核心应用)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title 逻辑合约 V1
contract LogicV1 {
    // 存储布局必须与代理合约一致!
    uint256 public value;
    address public admin;

    function setValue(uint256 _value) external {
        value = _value;
    }

    function getValue() external view returns (uint256) {
        return value;
    }

    function version() external pure returns (string memory) {
        return "V1";
    }
}

/// @title 逻辑合约 V2(升级后的版本)
contract LogicV2 {
    uint256 public value;
    address public admin;

    function setValue(uint256 _value) external {
        value = _value * 2; // V2: 存储值翻倍
    }

    function getValue() external view returns (uint256) {
        return value;
    }

    function version() external pure returns (string memory) {
        return "V2";
    }

    // V2 新增函数
    function increment() external {
        value += 1;
    }
}

/// @title 简易代理合约
/// @notice 所有调用通过 delegatecall 转发到逻辑合约
contract SimpleProxy {
    // 存储槽:与逻辑合约相同的布局
    uint256 public value;
    address public admin;

    // 逻辑合约地址(使用特殊存储槽避免冲突,简化版直接用变量)
    address public implementation;

    constructor(address _implementation) {
        implementation = _implementation;
        admin = msg.sender;
    }

    /// @notice 升级逻辑合约
    function upgradeTo(address newImplementation) external {
        require(msg.sender == admin, "Not admin");
        require(newImplementation != address(0), "Zero address");
        implementation = newImplementation;
    }

    /// @notice fallback:所有未匹配的调用都 delegatecall 到逻辑合约
    fallback() external payable {
        address impl = implementation;
        require(impl != address(0), "No implementation");

        assembly {
            // 复制 calldata 到内存
            calldatacopy(0, 0, calldatasize())

            // delegatecall 到逻辑合约
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

            // 复制返回数据
            returndatacopy(0, 0, returndatasize())

            switch result
            case 0 {
                // delegatecall 失败,revert
                revert(0, returndatasize())
            }
            default {
                // 成功,返回数据
                return(0, returndatasize())
            }
        }
    }

    receive() external payable {}
}

// 使用示例:
// 1. 部署 LogicV1
// 2. 部署 SimpleProxy(LogicV1.address)
// 3. 通过 Proxy 调用 setValue(42) → 实际修改的是 Proxy 的存储
// 4. 部署 LogicV2
// 5. 调用 proxy.upgradeTo(LogicV2.address)
// 6. 通过 Proxy 调用 setValue(42) → 现在存储的是 84(翻倍逻辑)

关键要点总结

要点说明
library 不能有状态变量纯工具函数集合
using A for B让类型 B 可以直接调用 A 的函数
call 推荐用于发送 ETHtransfer/send 的 2300 gas 限制太严格
delegatecall 存储上下文是调用方代理模式核心,但极度危险
staticcall 用于只读保证不会修改状态
存储布局必须一致delegatecall 时存储槽位必须与代理合约对齐
assembly 用于极致优化直接操作 EVM,节省 Gas 但牺牲可读性

常见误区

误区 1:delegatecall 的存储槽冲突

// 代理合约
contract Proxy {
    address public implementation; // slot 0
    address public admin;          // slot 1
}

// 逻辑合约
contract Logic {
    uint256 public value; // slot 0 → 会覆盖 Proxy 的 implementation!
}

// 解决方案:EIP-1967 使用随机存储槽
// bytes32 constant IMPLEMENTATION_SLOT =
//     bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);

误区 2:忘记检查 call 的返回值

// 危险!忽略返回值,ETH 可能发送失败但代码继续执行
target.call{value: 1 ether}("");

// 正确
(bool success, ) = target.call{value: 1 ether}("");
require(success, "Transfer failed");

误区 3:在 library 中使用 this

Library 中的 this 指向调用它的合约,而不是 library 自身。Library 没有自己的地址和存储。

误区 4:delegatecall 中 msg.sender 改变

// 用户 → ProxyA → LogicB (via delegatecall)
// 在 LogicB 中:
// msg.sender = 用户(不是 ProxyA!)
// address(this) = ProxyA(不是 LogicB!)

面试关联

Q: call 和 delegatecall 的区别?为什么 delegatecall 对代理模式至关重要?

30 秒回答call 在目标合约的存储上下文中执行代码,delegatecall 在调用方的存储上下文中执行目标合约的代码。代理模式需要 delegatecall,因为它允许代理合约使用逻辑合约的代码来操作自己的存储。这样升级时只需更换逻辑合约地址,用户数据(存储在代理合约中)不受影响。

追问:delegatecall 有什么安全风险?

  • 存储槽冲突:逻辑合约和代理合约的存储布局必须完全一致
  • 选择器冲突:代理合约和逻辑合约可能有同名函数
  • 初始化漏洞:逻辑合约的 constructor 不会在代理上下文中执行
  • 解决方案:EIP-1967 定义标准存储槽位,OpenZeppelin 的 TransparentProxy/UUPS 模式

Q: Solidity 中发送 ETH 的三种方式?推荐哪种?

  • transfer:2300 gas,失败 revert。不推荐,gas 限制太死板
  • send:2300 gas,失败返回 false。不推荐,容易忘检查返回值
  • call{value: ...}(""):转发所有 gas,失败返回 (false, data)。推荐,最灵活
  • 但用 call 时必须注意重入攻击,使用 CEI 模式或 ReentrancyGuard

参考资源

资源说明
Solidity Library 文档官方库文档
Yul 语言规范Assembly 语法参考
EIP-1967代理合约存储槽标准
OpenZeppelin Proxy代理合约最佳实践
Solidity by Example - Delegatecall图解 delegatecall
Trail of Bits - Building Secure Contracts安全最佳实践