1. EIP 1052:智能合约验证
智能合约能够通过检查另一个智能合约的哈希值来验证其本身。在君士坦丁堡分叉之前,智能合约必须提取另一个合约的完整代码才能进行验证,这种验证方式将耗费大量的时间和资源。
智能合约需要验证另一个智能合约的bytecode,但是其并不需要知道具体的bytecode本身。如,智能合约想验证另一合约的bytecode为one of a set of permitted implementations,或者其需要分析code,若分析通过,则将其列入白名单中。 在君士坦丁堡分叉之前,合约可使用EXTCODECOPY(0x3c) opcode来实现,但是对于大合约,该操作尤其昂贵。尽管事实上其仅需要hash值就足够了。为此,在EIP 1052中引入了EXTCODEHASH(0x3f) opcode,会返回合约bytecode对应的keccak256 hash值。
EXTCODEHASH 会从stake中取一个参数,然后在stack中存入:
- 其前96个bit置为0,
- 剩余的160bit为 the code of the account at the address对应的keccak256 hash值。
EXTCODEHASH(0x3f) 的运行结果分为:
- 若account不存在,或account为空,则执行结果为0;
- 若该account没有code,则为
keccak256(空)=c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 。可查看 SHA3在线生成器。
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
EXTCODEHASH 的gas cost为400,后通过EIP-1884将该gas cost提升为700。
EIP-1884 :给与默克尔树大小相关的操作码重新定价。 改变了一些 EVM 操作码的 Gas 耗用量,以防止滥发交易攻击并更好地平衡每个区块的计算开销。在以太坊网络上,一个操作所需耗用的 Gas 数量往往跟这个操作所需付出的计算开销相匹配。该 EIP 提高了一些计算密集但当前的 Gas 耗用量较少的操作码的耗用量,即 SLOAD、BALANCE 以及 EXTCODEHASH。
EIP-1884 修改定价后,可能会导致transfer 操作超过2300 gas limit,从而推荐使用transfer .{sendValue} 来转账,此时需注意reentrancy问题。
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*
* _Available since v2.4.0._
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
2. 可升级Proxy合约
可参考:
实现可升级合约主要有2大策略:
- 1)proxy合约
- 2)将logic 和 data 分开为不同的合约。
Proxy合约使用delegatecall opcode来forward function calls to 可升级的目标合约。由于delegatecall保留了the function call状态,因此,可更新目标合约的逻辑,而在proxy合约中保留了已更新目标合约逻辑的状态。由于使用了delegatecall,msg.sender将保留the caller of the proxy contract。
关于可升级Proxy代理合约模式,截止2020年3月,相关研究有:
3. EIP 1967:Standard Proxy Storage Slots
proxy合约广泛用于:
proxy合约会通过delegatecall 来调用logic合约。proxy合约会维护a persistent state (storage and balance) while the code delegated to the logic contract。
为了避免 proxy合约和logic合约 之间的存储使用冲突,logic合约 的地址通常保存在特定的storage slot 中,以保证编译器永远不会分配该插槽。EIP-1967 建议使用一组标准插槽来存储proxy信息。这允许像block explorers这样的客户端正确地提取并向最终用户显示这些信息,并允许logic合约选择性地对其进行操作。
具体为:
- Logic合约地址:通过
bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1) 获得。若为Beacon合约,则返回为空。 - Beacon合约地址:通过
bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) 获得。若为Logic合约,则此处返回为空。 - Admin地址:通过
bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) 获得。返回的为可升级logic合约的address。
contract Proxy {
// Used to store the address of the owner.
bytes32 private constant OWNER_POSITION = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
// Used to store the address of the implementation contract.
bytes32 private constant IMPLEMENTATION_POSITION = bytes32(
uint256(keccak256("eip1967.proxy.implementation")) - 1
);
......
.......
}
参考资料
[1] SHA3在线生成器 [2] EIP-1052 [3] EIP-1884 [4] 从历次升级看以太坊协议的演化 [5] Proxy示例Solidity合约代码 [6] Summary of Ethereum Upgradeable Smart Contract R&D — Part 1–2018 [7] Summary of Ethereum Upgradeable Smart Contract R&D — Part 2 — 2020 [8] EIP-1967
|