令人困惑的delegatecall实现

  • Deep Defi
  • 更新于 2022-12-03 22:25
  • 阅读 2209

动态代理的更优实现和使用注意

很多合约都有用到代理模式,一般通过delegatecall来调用逻辑合约的接口,很多代码的实现如下:

function _delegate(address implementation) internal {
  assembly {
    // The pointer to the free memory slot
    let ptr := mload(0x40)
    // Copy function signature and arguments from calldata at zero position into memory at pointer position
    calldatacopy(ptr, 0, calldatasize())
    // Delegatecall method of the implementation contract, returns 0 on error
    let result := delegatecall(gas(), implementation, ptr, calldatasize(), 0, 0)

    // Copy the size length of bytes from return data at zero position to pointer position
    returndatacopy(ptr, 0, returndatasize())
    // Depending on result value
    switch result
    case 0 {
      // End execution and revert state changes
      revert(ptr, returndatasize())
    }
    default {
      // Return data with length of size at pointers position
      return(ptr, returndatasize())
    }
  }
}

最近看到Openzepplin的实现:

function _delegate(address implementation) internal virtual {
  assembly {
    // Copy msg.data. We take full control of memory in this inline assembly
    // block because it will not return to Solidity code. We overwrite the
    // Solidity scratch pad at memory position 0.
    calldatacopy(0, 0, calldatasize())

    // Call the implementation.
    // out and outsize are 0 because we don't know the size yet.
    let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

    // Copy the returned data.
    returndatacopy(0, 0, returndatasize())

    switch result
    // delegatecall returns 0 on error.
    case 0 {
        revert(0, returndatasize())
    }
    default {
        return(0, returndatasize())
    }
  }
}

这里减少了一个获取free memory point的步骤,因此能节省一点gas。但是从0地址开始拷贝calldata不会破坏内存的数据吗?在Solidty Memory的章节中是这样描述内存的结构的:

Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:

- `0x00` - `0x3f` (64 bytes): scratch space for hashing methods
- `0x40` - `0x5f` (32 bytes): currently allocated memory size (aka. free memory pointer)
- `0x60` - `0x7f` (32 bytes): zero slot

Scratch space can be used between statements (i.e. within inline assembly). The zero slot is used as initial value for dynamic memory arrays and should never be written to (the free memory pointer points to `0x80` initially).

Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).

上面的内容描述了内存中有128个字节是保留的,有特殊的作用。比如0x40开始的32个字节存储的是fee memory point,因此我们经常能见到let ptr := mload(0x40)这样的代码。

笔者原本以为delegatecall会将逻辑合约的代码在当前调用环境下执行,即代码的执行过程中使用当前调用环境的stack和memory。显然这个认知是错误的,查看etherum的源码可以发现所有的合约调用(call,delegatecall,staticcall等)都会分配新的stack和memory:

func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
    // 略

    var (
        op          OpCode        // current opcode
        mem         = NewMemory() // bound memory
        stack       = newstack()  // local stack
        callContext = &ScopeContext{
            Memory:   mem,
            Stack:    stack,
            Contract: contract,
        }
        // For optimisation reason we're using uint64 as the program counter.
        // It's theoretically possible to go above 2^64. The YP defines the PC
        // to be uint256. Practically much less so feasible.
        pc   = uint64(0) // program counter
        cost uint64
        // copies used by tracer
        pcCopy  uint64 // needed for the deferred EVMLogger
        gasCopy uint64 // for EVMLogger to log gas remaining before execution
        logged  bool   // deferred EVMLogger should ignore already logged steps
        res     []byte // result of the opcode execution function
    )

  // 略
}

Openzepplin的实现虽然能节省一点gas,但是使用的时候需要注意前提条件,那就是调用_delegate的前后不应该有任何的内存操作。

比如:

wrongDelegatecall() external returns (bytes32) {
  // 0地址上的值会被覆盖掉
  assembly {
    mstore(0, 10)
  }
  _delegate(implementation);
  // zero slot可能被污染,会导致动态内存分配出现非预期结果
}
点赞 0
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Deep Defi
Deep Defi
0x824C...b1db
Bitcoin holder,Web3 developer。微信公众号Deep Defi。