.delegatecall(...)
.delegatecall() 是 Solidity 中的一个方法,用于从一个原始合约中调用目标合约中的一个函数。然而,与其他方法不同的是,当使用.delegatecall()在目标合约中执行函数时,上下文从原始合约中传递,即代码在目标合约中执行,但变量在原始合约中被修改。
通过本教程,我们将了解为什么正确理解.delegatecall()的工作原理很重要,否则会产生一些严重后果。
等等,什么?
让我们先了解它是如何工作的。
使用.delegatecall()时需要注意的是,原始合约的上下文被传递给目标合约,目标合约的所有状态变化都反映在原始合约的状态上,而不是目标合约的状态上,即使该函数是在目标合约上执行的。
嗯,不是很清楚,我明白你的意思。所以让我们试着通过一个例子来理解。
在以太坊中,一个函数可以表示为 4+32*N 个字节,其中 4 个字节为函数选择器,32*N 个字节为函数参数。
- 函数选择器。为了得到函数选择器,我们将函数的名称和它的参数类型进行散列,不留空隙,例如,对于像 
putValue(uint value)这样的东西,你将使用keccak-256散列putValue(uint),这是 Ethereum 使用的一个散列函数,然后取其前 4 个字节。为了更好地理解keccak-256和散列,我建议你观看这个视频 - 函数参数。将每个参数转换成固定长度为 32 字节的十六进制字符串,并将其连接起来。
 
我们有两个合约 Student.sol 和 Calculator.sol。我们不知道 Calculator.sol 的 ABI,但是我们知道他们存在一个 add 函数,这个函数接收两个 uint,并在 Calculator.sol 中把它们加起来。
让我们看看如何使用 delegateCall 来从 Student.sol 中调用这个函数
pragma solidity ^0.8.4;
contract Student {
    uint public mySum;
    address public studentAddress;
    function addTwoNumbers(address calculator, uint a, uint b) public returns (uint)  {
        (bool success, bytes memory result) = calculator.delegatecall(abi.encodeWithSignature("add(uint256,uint256)", a, b));
        require(success, "The call to calculator contract failed");
        return abi.decode(result, (uint));
    }
}
pragma solidity ^0.8.4;
contract Calculator {
    uint public result;
    address public user;
    function add(uint a, uint b) public returns (uint) {
        result = a + b;
        user = msg.sender;
        return result;
    }
}
我们的学生合约在这里有一个函数 addTwoNumbers,它接收一个地址和两个要相加的数字。它没有直接执行,而是试图在地址上做一个.delegatecall(),用于接收两个数字的函数 add。
我们使用了 abi.encodeWithSignature,也与 abi.encodeWithSelector 相同,它首先进行哈希运算,然后从函数的名称和参数类型中取出前 4 个字节。在我们的例子中,它做了以下工作。(bytes4(keccak256(add(uint,uint)),然后将参数--a,b 附加到函数选择器的 4 个字节上。这些都是 32 字节的长度(32 字节=256 位,这也是 uint256 可以存储的)。