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:
0xb63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76200000000000000000000000000000000000000000000000000000000
CreateX
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/sepolia
which returns:
0x608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564
and uint256(uint160(_singleton))
is simply:
cast abi-encode "fn(address)" "0x41675C099F32341bf84BFc5382aF534df5C7461a"
0x00000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a
Thus, 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,
values
is astruct
defined inCreateX
that encapsulates thepayable
amounts 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:
0xe96deee49f3f11d72d96910df008cfe3aba40f361d2eed0300a6e4f5165b067e44956c9b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000206608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f766964656400000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4b63e800d00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc540000000000000000000000000000000000000000000000000000000000000140000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f3f11d72d96910df008cfe3aba40f361d2eed030000000000000000000000000000000000000000000000000000000000000024fe51f64300000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Now 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_KEY
Example deployment: 0xa066d99c4b98544f1c27e8bcfb9643c94ec2908e50459e25795d4ed29261d47b
.