Skip to content

Commit 643a1df

Browse files
committed
fix: re-add create2factory
1 parent 04dde7d commit 643a1df

File tree

2 files changed

+151
-1
lines changed

2 files changed

+151
-1
lines changed

test/Factory.t.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.0;
33

44
import "./Base.t.sol";
5-
import "../script/Create2Factory.sol";
5+
import "./mocks/Create2Factory.sol";
66

77
contract MockModuleFoo {
88
uint256 public value;

test/mocks/Create2Factory.sol

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
pragma solidity 0.8.24;
2+
3+
/**
4+
* @title Immutable Create2 Contract Factory
5+
* @author 0age
6+
* @notice This contract provides a safeCreate2 function that takes a salt value
7+
* and a block of initialization code as arguments and passes them into inline
8+
* assembly. The contract prevents redeploys by maintaining a mapping of all
9+
* contracts that have already been deployed, and prevents frontrunning or other
10+
* collisions by requiring that the first 20 bytes of the salt are equal to the
11+
* address of the caller (this can be bypassed by setting the first 20 bytes to
12+
* the null address). There is also a view function that computes the address of
13+
* the contract that will be created when submitting a given salt or nonce along
14+
* with a given block of initialization code.
15+
* @dev CREATE2 will not be available on mainnet until (at least) block
16+
* 7,280,000. This contract has not yet been fully tested or audited - proceed
17+
* with caution and please share any exploits or optimizations you discover.
18+
*/
19+
contract ImmutableCreate2Factory {
20+
// mapping to track which addresses have already been deployed.
21+
mapping(address => bool) private _deployed;
22+
23+
function safeCreate2(
24+
bytes32 salt,
25+
bytes calldata initializationCode
26+
)
27+
external
28+
payable
29+
containsCaller(salt)
30+
returns (address deploymentAddress)
31+
{
32+
// move the initialization code from calldata to memory.
33+
bytes memory initCode = initializationCode;
34+
35+
// determine the target address for contract deployment.
36+
address targetDeploymentAddress = address(
37+
uint160( // downcast to match the address type.
38+
uint256( // convert to uint to truncate upper digits.
39+
keccak256( // compute the CREATE2 hash using 4 inputs.
40+
abi.encodePacked( // pack all inputs to the hash together.
41+
hex"ff", // start with 0xff to distinguish from RLP.
42+
address(this), // this contract will be the caller.
43+
salt, // pass in the supplied salt value.
44+
keccak256( // pass in the hash of initialization code.
45+
abi.encodePacked(initCode))
46+
)
47+
)
48+
)
49+
)
50+
);
51+
52+
// ensure that a contract hasn't been previously deployed to target address.
53+
require(!_deployed[targetDeploymentAddress], "Invalid contract creation - contract has already been deployed.");
54+
55+
// using inline assembly: load data and length of data, then call CREATE2.
56+
assembly {
57+
// solhint-disable-line
58+
let encoded_data := add(0x20, initCode) // load initialization code.
59+
let encoded_size := mload(initCode) // load the init code's length.
60+
deploymentAddress :=
61+
create2( // call CREATE2 with 4 arguments.
62+
callvalue(), // forward any attached value.
63+
encoded_data, // pass in initialization code.
64+
encoded_size, // pass in init code's length.
65+
salt // pass in the salt value.
66+
)
67+
}
68+
69+
// check address against target to ensure that deployment was successful.
70+
require(deploymentAddress == targetDeploymentAddress, "Failed to deploy contract using provided salt and initialization code.");
71+
72+
// record the deployment of the contract to prevent redeploys.
73+
_deployed[deploymentAddress] = true;
74+
}
75+
76+
function findCreate2Address(bytes32 salt, bytes calldata initCode) external view returns (address deploymentAddress) {
77+
// determine the address where the contract will be deployed.
78+
deploymentAddress = address(
79+
uint160( // downcast to match the address type.
80+
uint256( // convert to uint to truncate upper digits.
81+
keccak256( // compute the CREATE2 hash using 4 inputs.
82+
abi.encodePacked( // pack all inputs to the hash together.
83+
hex"ff", // start with 0xff to distinguish from RLP.
84+
address(this), // this contract will be the caller.
85+
salt, // pass in the supplied salt value.
86+
keccak256( // pass in the hash of initialization code.
87+
abi.encodePacked(initCode))
88+
)
89+
)
90+
)
91+
)
92+
);
93+
94+
// return null address to signify failure if contract has been deployed.
95+
if (_deployed[deploymentAddress]) {
96+
return address(0);
97+
}
98+
}
99+
100+
function findCreate2AddressViaHash(bytes32 salt, bytes32 initCodeHash) external view returns (address deploymentAddress) {
101+
// determine the address where the contract will be deployed.
102+
deploymentAddress = address(
103+
uint160( // downcast to match the address type.
104+
uint256( // convert to uint to truncate upper digits.
105+
keccak256( // compute the CREATE2 hash using 4 inputs.
106+
abi.encodePacked( // pack all inputs to the hash together.
107+
hex"ff", // start with 0xff to distinguish from RLP.
108+
address(this), // this contract will be the caller.
109+
salt, // pass in the supplied salt value.
110+
initCodeHash // pass in the hash of initialization code.
111+
)
112+
)
113+
)
114+
)
115+
);
116+
117+
// return null address to signify failure if contract has been deployed.
118+
if (_deployed[deploymentAddress]) {
119+
return address(0);
120+
}
121+
}
122+
123+
/**
124+
* @dev Determine if a contract has already been deployed by the factory to a
125+
* given address.
126+
* @param deploymentAddress address The contract address to check.
127+
* @return True if the contract has been deployed, false otherwise.
128+
*/
129+
function hasBeenDeployed(address deploymentAddress) external view returns (bool) {
130+
// determine if a contract has been deployed to the provided address.
131+
return _deployed[deploymentAddress];
132+
}
133+
134+
/**
135+
* @dev Modifier to ensure that the first 20 bytes of a submitted salt match
136+
* those of the calling account. This provides protection against the salt
137+
* being stolen by frontrunners or other attackers. The protection can also be
138+
* bypassed if desired by setting each of the first 20 bytes to zero.
139+
* @param salt bytes32 The salt value to check against the calling address.
140+
*/
141+
modifier containsCaller(bytes32 salt) {
142+
// prevent contract submissions from being stolen from tx.pool by requiring
143+
// that the first 20 bytes of the submitted salt match msg.sender.
144+
require(
145+
(address(bytes20(salt)) == msg.sender) || (bytes20(salt) == bytes20(0)),
146+
"Invalid salt - first 20 bytes of the salt must match calling address."
147+
);
148+
_;
149+
}
150+
}

0 commit comments

Comments
 (0)