<aside> 📘 Relates:
Solidity layout and access of storage state variables simply explained
Solidity delegatecall usage and pitfalls
Understanding Smart Contract Vulnerabilities
</aside>
<aside> 📘 TL;DR;
按照惯例,用 proxy(文中称为 EntryPointContract) 和 implementation(文中称为 DelegateContract) 来指代入口和逻辑合约。
proxy contract
本文介绍了 slots 的映射规则,就记住如下几点即可:
按照 openzepplin 的设计,proxy 内只放三个变量(implementation、admin、beacon),其他所有的变量,尽可能地放到 implementation 内。这样做可以简化复杂度,就是需要注意升级 implementation 时需要对齐新旧版本的 slots。
</aside>
Solidity delegatecall
is a low-level function that allows us to load and call the code of another contract while preserving the context of the main contract. This means that the code of the called contract is executed but any state changes made by the called contract are actually made on the main contract storage, not on the called contract’s storage.
This is useful to create libraries and the proxy contract pattern, where we delegate the call to a different contract, “giving it” permission to modify the state of the calling contract.
This function also has some pitfalls that we need to be aware of and is basically what this article will focus on.
As explained in another article regarding the layout of state variables in storage, every state variable declared in a contract occupies a slot in the storage, potentially sharing a common slot with other state variables if their type is smaller than 32 bytes and they can fit together in a single slot.
Solidity layout and access of storage state variables simply explained
So, when we access these state variables, to assign values to them or to read from them, Solidity uses the declaration position of that state variable to know what storage slot to access and read from it or update it.
For example, given the following contract:
contract EntryPointContract {
address public owner = msg.sender;
uint256 public id = 5;
uint256 public updatedAt = block.timestamp;
}
We see that it declares 3 state variables, owner
, id
and updatedAt
. These state variables have some values assigned, and in storage, they would look something like this:
We see that at slot index 0 we have the value of the first state variable, left padded with zeros because each slot holds 32 bytes of data.
The second slot, which has index 1, has the value of the id
state variable.
The third slot, with index 2, has the value o the third state variable updatedAt
. All the data in storage is represented as hexadecimal, so converting 0x62fc3adb
to decimal is 1660697307, which converted to date with js:
const date = new Date(1660697307 * 1000);
console.log(date)
Produces:
Tue Aug 16 2022 20:48:27 GMT-0400 (Atlantic Standard Time))
So, when accessing the state variable id
, we are accessing the slot with index 1.