{"content":{"title":"令人困惑的delegatecall实现","body":"很多合约都有用到代理模式，一般通过delegatecall来调用逻辑合约的接口，很多代码的实现如下：\r\n\r\n```solidity\r\nfunction _delegate(address implementation) internal {\r\n  assembly {\r\n    // The pointer to the free memory slot\r\n    let ptr := mload(0x40)\r\n    // Copy function signature and arguments from calldata at zero position into memory at pointer position\r\n    calldatacopy(ptr, 0, calldatasize())\r\n    // Delegatecall method of the implementation contract, returns 0 on error\r\n    let result := delegatecall(gas(), implementation, ptr, calldatasize(), 0, 0)\r\n\r\n    // Copy the size length of bytes from return data at zero position to pointer position\r\n    returndatacopy(ptr, 0, returndatasize())\r\n    // Depending on result value\r\n    switch result\r\n    case 0 {\r\n      // End execution and revert state changes\r\n      revert(ptr, returndatasize())\r\n    }\r\n  \tdefault {\r\n      // Return data with length of size at pointers position\r\n      return(ptr, returndatasize())\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n最近看到Openzepplin的实现：\r\n\r\n```solidity\r\nfunction _delegate(address implementation) internal virtual {\r\n  assembly {\r\n    // Copy msg.data. We take full control of memory in this inline assembly\r\n    // block because it will not return to Solidity code. We overwrite the\r\n    // Solidity scratch pad at memory position 0.\r\n    calldatacopy(0, 0, calldatasize())\r\n\r\n    // Call the implementation.\r\n    // out and outsize are 0 because we don't know the size yet.\r\n    let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\r\n\r\n    // Copy the returned data.\r\n    returndatacopy(0, 0, returndatasize())\r\n\r\n    switch result\r\n    // delegatecall returns 0 on error.\r\n    case 0 {\r\n    \trevert(0, returndatasize())\r\n    }\r\n    default {\r\n    \treturn(0, returndatasize())\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n这里减少了一个获取`free memory point`的步骤，因此能节省一点gas。但是从0地址开始拷贝calldata不会破坏内存的数据吗？在Solidty   Memory的章节中是这样描述内存的结构的：\r\n\r\n```markdown\r\nSolidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:\r\n\r\n- `0x00` - `0x3f` (64 bytes): scratch space for hashing methods\r\n- `0x40` - `0x5f` (32 bytes): currently allocated memory size (aka. free memory pointer)\r\n- `0x60` - `0x7f` (32 bytes): zero slot\r\n\r\nScratch 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).\r\n\r\nSolidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).\r\n```\r\n\r\n上面的内容描述了内存中有128个字节是保留的，有特殊的作用。比如0x40开始的32个字节存储的是`fee memory point`，因此我们经常能见到`let ptr := mload(0x40)`这样的代码。\r\n\r\n笔者原本以为delegatecall会将逻辑合约的代码在当前调用环境下执行，即代码的执行过程中使用当前调用环境的stack和memory。显然这个认知是错误的，查看etherum的[源码](https://github.com/ethereum/go-ethereum/blob/e24d6003b180bba0337a57b0c2eaf3966cf4d268/core/vm/interpreter.go#L112)可以发现所有的合约调用（call,delegatecall,staticcall等）都会分配新的stack和memory：\r\n\r\n```go\r\nfunc (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {\r\n\t// 略\r\n\r\n\tvar (\r\n\t\top          OpCode        // current opcode\r\n\t\tmem         = NewMemory() // bound memory\r\n\t\tstack       = newstack()  // local stack\r\n\t\tcallContext = &ScopeContext{\r\n\t\t\tMemory:   mem,\r\n\t\t\tStack:    stack,\r\n\t\t\tContract: contract,\r\n\t\t}\r\n\t\t// For optimisation reason we're using uint64 as the program counter.\r\n\t\t// It's theoretically possible to go above 2^64. The YP defines the PC\r\n\t\t// to be uint256. Practically much less so feasible.\r\n\t\tpc   = uint64(0) // program counter\r\n\t\tcost uint64\r\n\t\t// copies used by tracer\r\n\t\tpcCopy  uint64 // needed for the deferred EVMLogger\r\n\t\tgasCopy uint64 // for EVMLogger to log gas remaining before execution\r\n\t\tlogged  bool   // deferred EVMLogger should ignore already logged steps\r\n\t\tres     []byte // result of the opcode execution function\r\n\t)\r\n\t\r\n  // 略\r\n}\r\n```\r\n\r\nOpenzepplin的实现虽然能节省一点gas，但是使用的时候需要注意前提条件，那就是调用`_delegate`的前后不应该有任何的内存操作。\r\n\r\n比如：\r\n\r\n```solidity\r\nwrongDelegatecall() external returns (bytes32) {\r\n  // 0地址上的值会被覆盖掉\r\n  assembly {\r\n    mstore(0, 10)\r\n  }\r\n  _delegate(implementation);\r\n  // zero slot可能被污染，会导致动态内存分配出现非预期结果\r\n}\r\n```"},"author":{"user":"https://learnblockchain.cn/people/7938","address":null},"history":null,"timestamp":1670077503,"version":1}