SafeProxyFactory 创建 SafeProxy 并且 SafeProxy 继承 Safe

SafeProxyFactory 创建 SafeProxy 并且 SafeProxy 继承 Safe 所有方法的关键在于 代理合约(Proxy Pattern),特别是 SafeProxy 充当代理,并将调用委托给 Safe。让我们解析一下核心机制:


1. SafeProxy 是如何继承 Safe 方法的?

SafeProxy 其实并没有直接继承 Safe,而是通过 代理(Proxy)模式,利用 delegatecallSafeProxy 具备 Safe 的所有方法。

SafeProxy 的核心逻辑通常在其 fallback 函数内,通过 delegatecall 把调用转发给 _singleton(也就是 Safe),让 SafeProxy 看起来像是 Safe,但实际上只是一个空壳:

contract SafeProxy {
    address internal singleton;

    constructor(address _singleton) {
        require(_singleton != address(0), "Invalid singleton address");
        singleton = _singleton;
    }

    fallback() external payable {
        // 将调用代理到 singleton(Safe 合约)
        assembly {
            let _singleton := sload(0) // 读取 singleton 地址
            calldatacopy(0, 0, calldatasize()) // 复制 calldata
            let result := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize()) // 复制返回数据
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

2. deployProxy 是如何让 SafeProxy 具备 Safe 方法的?

deployProxy 方法中,我们关注以下关键点:

(1) 通过 CREATE2 创建 SafeProxy

bytes memory deploymentData = abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
assembly {
    proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
}
  • type(SafeProxy).creationCode:获取 SafeProxy 的合约代码。
  • abi.encodePacked(..., uint256(uint160(_singleton))):将 SafeProxy 代码和 _singleton 地址编码到一起。
  • create2(...):使用 CREATE2 部署 SafeProxy,确保地址可预测。

关键点:这里 _singleton 传入的是 Safe 的地址,意味着 SafeProxy 内部的 singleton 变量会被初始化为 Safe 的地址。

(2) SafeProxy 代理 Safe

SafeProxy 被创建后,它的 fallback 会将所有调用 delegatecall_singleton,即 Safe。因此:

  • 从外部调用 SafeProxy,会通过 fallback 进入 delegatecall,相当于直接调用 Safe 本体。
  • delegatecallSafeProxy 继承 Safe 的所有逻辑,但存储变量仍然在 SafeProxy 内部。

3. initializer 作用

 
if (initializer.length > 0) {
    assembly {
        if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
            revert(0, 0)
        }
    }
}
  • initializer 允许在部署后立即执行初始化,通常用于 Safesetup 方法,以设置 owners、threshold、fallback handler 等信息。
  • 这避免了代理合约刚部署时处于未初始化状态,防止攻击者利用 delegatecall 进行恶意初始化(类似 EIP-1967 的安全风险)。
    •  
    • 使用 call 执行 initializer
      • initializer 是一个 bytes 类型的 calldata,它通常是 Safesetup 方法的 ABI 编码参数。
      • call 发送交易到 proxy(SafeProxy),执行 initializer 指定的初始化逻辑。
    • 如果 call 失败,则回滚整个交易

    各参数解析

    solidity
    call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0)

    这是一个 call 低级操作,Solidity call 语法如下:

    solidity
    success := call(gas, to, value, inOffset, inSize, outOffset, outSize)

    参数解析:

    参数 作用
    gas() 传递当前剩余 Gas 让 proxy 运行 initializer
    proxy 目标合约地址,即 SafeProxy 实例
    0 发送的 ETH 数量,这里是 0
    add(initializer, 0x20) initializer 的数据指针,跳过前 32 字节长度信息
    mload(initializer) initializer 的数据长度
    0 返回数据存储的内存位置(这里忽略)
    0 返回数据的最大长度(这里忽略)

总结

  1. SafeProxy 只是一个壳,所有方法都通过 delegatecall 代理到 Safe
  2. deployProxy 通过 CREATE2 创建 SafeProxy,并设置 Safe 作为 singleton
  3. SafeProxyfallback 函数拦截所有调用,并 delegatecallSafe,让它表现得像 Safe
  4. initializer 允许部署时执行 Safesetup,确保合约立即可用。

最终结果: 通过 SafeProxy 交互,相当于直接调用 Safe,但存储数据仍然保留在 SafeProxy 自己的存储空间内,这就是 代理模式(Proxy Pattern) 的强大之处。 🚀

posted @ 2025-03-04 22:48  若-飞  阅读(55)  评论(0)    收藏  举报