Safe 1.4.1 Deployment via CreateX
Special thanks go to Richard Meissner for feedback and review.
Important
This approach requires a deep understanding of how the Safe contracts and CreateX mechanisms work. The recommended way to deploy a Safe contract remains through the SafeProxyFactory contract.
We will deploy a 1-out-of-1 1.4.1 Safe contract via CreateX.
Firstly, we need to make sure that the setup function of the Safe contract is called with the correct parameters:
/**
* @notice Sets an initial storage of the Safe contract.
* @dev This method can only be called once.
* If a proxy was created without setting up, anyone can call setup and claim the proxy.
* @param _owners List of Safe owners.
* @param _threshold Number of required confirmations for a Safe transaction.
* @param to Contract address for optional delegate call.
* @param data Data payload for optional delegate call.
* @param fallbackHandler Handler for fallback calls to this contract
* @param paymentToken Token that should be used for the payment (0 is ETH)
* @param payment Value that should be paid
* @param paymentReceiver Address that should receive the payment (or 0 if tx.origin)
*/
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external {
// setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice
setupOwners(_owners, _threshold);
if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
// As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
setupModules(to, data);
if (payment > 0) {
// To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself)
// baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment
handlePayment(payment, 0, 1, paymentToken, paymentReceiver);
}
emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);
}_owners=[0xYourSignerAddress],_threshold=1,to=0xBD89A1CE4DDe368FFAB0eC35506eEcE0b1fFdc54(SafeToL2Setup),data=cast calldata "setupToL2(address)" "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762"(SafeL2) =0xfe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762,fallbackHandler=0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99(CompatibilityFallbackHandler),paymentToken=0x0000000000000000000000000000000000000000,uint256=0,paymentReceiver=0x0000000000000000000000000000000000000000.
Example:
cast calldata "setup(address[],uint256,address,bytes,address,address,uint256,address)" \
"[0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03]" "1" "0xBD89A1CE4DDe368FFAB0eC35506eEcE0b1fFdc54" \
"0xfe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762" \
"0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99" "0x0000000000000000000000000000000000000000" \
"0" "0x0000000000000000000000000000000000000000"returns:
0xb63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000CreateX Deployment
We will call the CreateX function deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)):
/**
* @dev Deploys and initialises a new contract via calling the `CREATE2` opcode and using the
* salt value `salt`, creation bytecode `initCode`, the initialisation code `data`, the struct for
* the `payable` amounts `values`, and `msg.value` as inputs. In order to save deployment costs,
* we do not sanity check the `initCode` length. Note that if `values.constructorAmount` is non-zero,
* `initCode` must have a `payable` constructor, and any excess ether is returned to `msg.sender`.
* @param salt The 32-byte random value used to create the contract address.
* @param initCode The creation bytecode.
* @param data The initialisation code that is passed to the deployed contract.
* @param values The specific `payable` amounts for the deployment and initialisation call.
* @return newContract The 20-byte address where the contract was deployed.
* @custom:security This function allows for reentrancy, however we refrain from adding
* a mutex lock to keep it as use-case agnostic as possible. Please ensure at the protocol
* level that potentially malicious reentrant calls do not affect your smart contract system.
*/
function deployCreate2AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values
) public payable returns (address newContract) {
// Note that the safeguarding function `_guard` is called as part of the overloaded function
// `deployCreate2AndInit`.
newContract = deployCreate2AndInit({
salt: salt,
initCode: initCode,
data: data,
values: values,
refundAddress: msg.sender
});
}Important
Do NOT skip the following section! Read it carefully otherwise you put funds at risk on other chains.
It's important that we configure a permissioned deploy protection to prevent (malicious) actors from frontrunning and deploying a differently configured Safe contract on another chain:
salt=0xYourDeployerAddress || 00 || random11Bytes=0x9F3f11d72d96910df008Cfe3aBA40F361D2EED0300a6e4f5165b067e44956c9b(example).
The next step is to retrieve the initCode parameter:
initCode=abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)))(SafeProxyFactory)
To compute the initCode, we first obtain the contract creation code via (0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67 is the 1.4.1 SafeProxyFactory):
cast call 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67 "proxyCreationCode()(bytes)" --rpc-url https://1rpc.io/sepoliawhich returns:
0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564and uint256(uint160(_singleton)) is simply:
cast abi-encode "fn(address)" "0x41675C099F32341bf84BFc5382aF534df5C7461a"0x00000000000000000000000041675c099f32341bf84bfc5382af534df5c7461aThus, we have:
initCode=0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f766964656400000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a,data=YourEncodedSetupFunctionCall=0xb63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000(example),values=cast abi-encode "Values((uint256,uint256))" "(0,0)"=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000(optional, can be directly encoded in the next step).
For context,
valuesis astructdefined inCreateXthat encapsulates thepayableamounts required for the contract deployment and subsequent initialisation call:
/**
* @dev Struct for the `payable` amounts in a deploy-and-initialise call.
*/
struct Values {
uint256 constructorAmount;
uint256 initCallAmount;
}At this point, we can fully encode the deployCreate2AndInit(bytes32,bytes,bytes,tuple(uint256,uint256)) function call (example):
cast calldata "deployCreate2AndInit(bytes32,bytes,bytes,(uint256,uint256))" \
"0x9F3f11d72d96910df008Cfe3aBA40F361D2EED0300a6e4f5165b067e44956c9b" \
"0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f766964656400000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a" \
"0xb63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000" "(0,0)"returns:
0xe96deee49f3f11d72d96910df008cfe3aba40f361d2eed0300a6e4f5165b067e44956c9b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000206608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f766964656400000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4b63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Now we are finally ready to deploy it (the canonical CreateX address is 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed):
cast send 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed \
0xe96deee49f3f11d72d96910df008cfe3aba40f361d2eed0300a6e4f5165b067e44956c9b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000206608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f766964656400000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4b63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--rpc-url https://1rpc.io/sepolia --private-key $PRIVATE_KEYExample deployment: 0xa066d99c4b98544f1c27e8bcfb9643c94ec2908e50459e25795d4ed29261d47b.