通过本次闯关,让我们深入理解 calldata 数据编码。
Ethernaut 闯关投稿到 Ethernaut 题库闯关 专栏, 欢迎订阅,本题是一个名为 Switch 的合约,把 switchOn 设置为 true 则挑战成功, 难度等级:难。
Switch 合约如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Switch {
    bool public switchOn; // switch is off
    bytes4 public offSelector = bytes4(keccak256("turnSwitchOff()"));
     modifier onlyThis() {
        require(msg.sender == address(this), "Only the contract can call this");
        _;
    }
    modifier onlyOff() {
        //  将复杂的数据类型放入内存中
        bytes32[1] memory selector;
        // 检查位置(_data的位置)68处的calldata数据
        assembly {
            calldatacopy(selector, 68, 4) // //从calldata中抽取出函数选择器
        }
        require(
            selector[0] == offSelector,
            "Can only call the turnOffSwitch function"
        );
        _;
    }
    function flipSwitch(bytes memory _data) public onlyOff {
        (bool success, ) = address(this).call(_data);
        require(success, "call failed :(");
    }
    function turnSwitchOn() public onlyThis {
        switchOn = true;
    }
    function turnSwitchOff() public onlyThis {
        switchOn = false;
    }
}通过本挑战,更好地让我们跟深刻的理解 calldata Calldata的编码。
Switch 合约只有这3个可以从外部调用的函数:flipSwitch、turnSwitchOn和turnSwitchOff。
但是flipSwitch 是唯一可以调用的函数,因为turnSwitchOn和turnSwitchOff只有在msg.sender是当前合约时才能访问(因为onlyThis 修改器)。
让我们来看看你可以调用的函数:
 function flipSwitch(bytes memory _data) public onlyOff {
        (bool success, ) = address(this).call(_data);
        require(success, "call failed :(");
    }你可以看到flipSwitch有一个函数修改器onlyOff, 此函数修改器对Calldata(calldata)进行检查。
modifier onlyOff() {
        // 将复杂的数据类型放入内存中
        bytes32[1] memory selector;
        // 检查位置(_data的位置)68处的calldata数据
        assembly {
            calldatacopy(selector, 68, 4) //从calldata中抽取函数选择器
        }
        require(
            selector[0] == offSelector,
            "Can only call the turnOffSwitch function"
        );
        _;
    }函数修改器检查从位置 68 开始并且长度为4字节的数据是否是turnOffSwitch函数的选择器。
乍一看 ,flipSwitch只能在turnSwitchOff作为数据的情况下调用,但通过操纵calldata编码,你会发现不是这样的。
如何解题呢?如果你要挑战一下,可以暂停一下...
静态类型如下:
uint 等int 等addressboolbytes32 等tuples 等这些类型的表示是用十六进制表示的,用零填充以覆盖32字节。
Input: 23 (uint256)
Output:
0x000000000000000000000000000000000000000000000000000000000000002aInput: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f (address of Uniswap)
Output: 
0x000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f对于动态类型,calldata编码基于以下内容:
Input: 0x123
Output: 
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000其中:
offset:
0000000000000000000000000000000000000000000000000000000000000020
length(the value is 2 bytes length = 4 chrs):
0000000000000000000000000000000000000000000000000000000000000002
value(the value of string and bytes starts right after the length):
12340000000000000000000000000000000000000000000000000000000000002. String:
Input: “GM Frens”
Output: 
0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008474d204672656e73000000000000000000000000000000000000000000000000其中:
offset:
0000000000000000000000000000000000000000000000000000000000000020 
length:
000000000000000000000000000000000000000000000000000000... 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!