这是大家最常提及的bytecode,用来生成runtime bytecode,包括了构造函数逻辑和逻辑函数参数。在solidity里可以用type(ContractName).creationCode
来获取。当你编译合约时,creation bytecode就会生成。在Remix里可以用compilation details
里查看,如下图,划红线的就是creation bytecode。
这是存储在链上用来描述智能合约的代码。这个代码不包括构造函数逻辑和参数,实际上是creation bytecode在evm里执行完后的返回的字节码。下文讲述的metamorphic合约就是利用了这个性质来实现用不同的bytecode部署相同地址的合约的。
合约的runtime bytecode可以在链上用extcodecopy
获取,在solidity里可以用type(ContractName).runtimeCode
来获取,runtime bytecode的hash可以用extcodehash
获取。
这是一个概括性的术语,包括了runtime bytecode和creation byte,但更通常用来表示runtime bytecode。
实际上也是creation bytecode,这个术语在create2
opcode的文档里有提及。
在Constantinople硬分叉后,EVM增加了create2
这个opcode,允许传入待创建合约的creation bytecode和salt,部署合约在指定的地址,这个地址可以用keccak256(0xff ++ deployersAddr ++ salt ++ keccak256(bytecode))[12:]
计算出来。而EVM里还有另外一个opcode,叫做selfdestruct
,可以用来销毁当前合约,销毁后合约的runtime code会变成0,而且storage也会全部清空。
Metamorphic合约利用了上述两个特性,非常巧妙的实现了合约的升级。
//SPDX-License-Identifier: MIT
pragma solidity 0.8.1;
contract Factory {
mapping (address => address) _implementations;
event Deployed(address _addr);
function deploy(uint salt, bytes calldata bytecode) public {
bytes memory implInitCode = bytecode;
/*
* Metamorphic contract initialization code (29 bytes):
*
* 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
*
* Description:
*
* pc|op|name | [stack] | <memory>
*
* ** set the first stack item to zero - used later **
* 00 58 getpc [0] <>
*
* ** set second stack item to 32, length of word returned from staticcall **
* 01 60 push1
* 02 20 outsize [0, 32] <>
*
* ** set third stack item to 0, position of word returned from staticcall **
* 03 81 dup2 [0, 32, 0] <>
*
* ** set fourth stack item to 4, length of selector given to staticcall **
* 04 58 getpc [0, 32, 0, 4] <>
*
* ** set fifth stack item to 28, position of selector given to staticcall **
* 05 60 push1
* 06 1c inpos [0, 32, 0, 4, 28] <>
*
* ** set the sixth stack item to msg.sender, target address for staticcall **
* 07 33 caller [0, 32, 0, 4, 28, caller] <>
*
* ** set the seventh stack item to msg.gas, gas to forward for staticcall **
* 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>
*
* ** set the eighth stack item to selector, "what" to store via mstore **
* 09 63 push4
* 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>
*
* ** set the ninth stack item to 0, "where" to store via mstore ***
* 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>
*
* ** call mstore, consume 8 and 9 from the stack, place selector in memory **
* 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>
*
* ** call staticcall, consume items 2 through 7, place address in memory **
* 13 fa staticcall [0, 1 (if successful)] <address>
*
* ** flip success bit in second stack item to set to 0 **
* 14 15 iszero [0, 0] <address>
*
* ** push a third 0 to the stack, position of address in memory **
* 15 81 dup2 [0, 0, 0] <address>
*
* ** place address from position in memory onto third stack item **
* 16 51 mload [0, 0, address] <>
*
* ** place address to fourth stack item for extcodesize to consume **
* 17 80 dup1 [0, 0, address, address] <>
*
* ** get extcodesize on fourth stack item for extcodecopy **
* 18 3b extcodesize [0, 0, address, size] <>
*
* ** dup and swap size for use by return at end of init code **
* 19 80 dup1 [0, 0, address, size, size] <>
* 20 93 swap4 [size, 0, address, size, 0] <>
*
* ** push code position 0 to stack and reorder stack items for extcodecopy **
* 21 80 dup1 [size, 0, address, size, 0, 0] <>
* 22 91 swap2 [size, 0, address, 0, 0, size] <>
* 23 92 swap3 [size, 0, size, 0, 0, address] <>
*
* ** call extcodecopy, consume four items, clone runtime code to memory **
* 24 3c extcodecopy [size, 0] <code>
*
* ** return to deploy final code in memory **
* 25 f3 return [] *deployed!*
*/
bytes memory metamorphicCode = (
hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"
);
// determine the address of the metamorphic contract.
address metamorphicContractAddress = _getMetamorphicContractAddress(salt, metamorphicCode);
// declare a variable for the address of the implementation contract.
address implementationContract;
// load implementation init code and length, then deploy via CREATE.
/* solhint-disable no-inline-assembly */
assembly {
let encoded_data := add(0x20, implInitCode) // load initialization code.
let encoded_size := mload(implInitCode) // load init code's length.
implementationContract := create( // call CREATE with 3 arguments.
0, // do not forward any endowment.
encoded_data, // pass in initialization code.
encoded_size // pass in init code's length.
)
} /* solhint-enable no-inline-assembly */
//first we deploy the code we want to deploy on a separate address
// store the implementation to be retrieved by the metamorphic contract.
_implementations[metamorphicContractAddress] = implementationContract;
address addr;
assembly {
let encoded_data := add(0x20, metamorphicCode) // load initialization code.
let encoded_size := mload(metamorphicCode) // load init code's length.
addr := create2(0, encoded_data, encoded_size, salt)
}
require(
addr == metamorphicContractAddress,
"Failed to deploy the new metamorphic contract."
);
emit Deployed(addr);
}
/**
* @dev Internal view function for calculating a metamorphic contract address
* given a particular salt.
*/
function _getMetamorphicContractAddress(
uint256 salt,
bytes memory metamorphicCode
) internal view returns (address) {
// determine the address of the metamorphic contract.
return address(
uint160( // downcast to match the address type.
uint256( // convert to uint to truncate upper digits.
keccak256( // compute the CREATE2 hash using 4 inputs.
abi.encodePacked( // pack all inputs to the hash together.
hex"ff", // start with 0xff to distinguish from RLP.
address(this), // this contract will be the caller.
salt, // pass in the supplied salt value.
keccak256(
abi.encodePacked(
metamorphicCode
)
) // the init code hash.
)
)
)
)
);
}
//those two functions are getting called by the metamorphic Contract
function getImplementation() external view returns (address implementation) {
return _implementations[msg.sender];
}
}
contract Test1 {
uint public myUint;
function setUint(uint _myUint) public {
myUint = _myUint;
}
function killme() public {
selfdestruct(payable(msg.sender));
}
}
contract Test2 {
uint public myUint;
function setUint(uint _myUint) public {
myUint = 2*_myUint;
}
function killme() public {
selfdestruct(payable(msg.sender));
}
}
5860208158601c335a63aaf10f428752fa158151803b80938091923cf3,这串bytecode的原理是staticcall调用getImplementation方法,获取implementation合约地址,再用extcodecopy把implementation合约的runtime bytecode复制到memory,做为当前部署合约的runtime bytecode,以此来动态替换合约的runtime bytecode,而合约地址又不变。
Factory
合约Test1
的bytecode和一个固定的salt,调用Factory
合约的deploy方法部署Test1
合约,Deployed事件的参数就是Test1
合约的地址Test1
合约的killme方法,销毁合约Test2
的bytecode和部署Test1
时相同的salt,调用Factory
合约的deploy方法部署Test2
合约,Deployed事件的参数就是Test2
合约的地址审计合约里有没有出现selfdestruct,或者通过delegatecall或者callcode去调用selfdestruct。因为不能销毁也就意味着不能重新部署。如果存在selfdestruct,那要检查合约的部署者是否用create2创建此合约,如果是用create2创建的,就要辨别一下部署器是不是metamorphic合约,如果是那要小心了。