Skip to content

Commit fbcca19

Browse files
authored
refactor: deploys with createX (#151)
Closes BES-731
1 parent fdf1fbc commit fbcca19

File tree

19 files changed

+290
-86
lines changed

19 files changed

+290
-86
lines changed

foundry.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ solc_version = '0.8.30'
1313
libs = ['node_modules', 'lib']
1414
optimizer_runs = 10_000
1515
evm_version = 'prague'
16-
always_use_create_2_factory = true
1716

1817
[profile.optimized]
1918
via_ir = true

script/Constants.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ISafe} from '@safe-smart-account/interfaces/ISafe.sol';
55
import {MultiSendCallOnly} from '@safe-smart-account/libraries/MultiSendCallOnly.sol';
66
import {SafeProxyFactory} from '@safe-smart-account/proxies/SafeProxyFactory.sol';
77
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
8+
import {ICreateX} from 'interfaces/external/ICreateX.sol';
89

910
/**
1011
* @title Constants
@@ -17,6 +18,9 @@ abstract contract Constants {
1718
MultiSendCallOnly public constant MULTI_SEND_CALL_ONLY =
1819
MultiSendCallOnly(0x9641d764fc13c8B624c04430C7356C1C7C8102e2);
1920

21+
// CreateX
22+
ICreateX public constant CREATE_X = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
23+
2024
// Wonderland Canon Guard
2125
ISafe public constant SAFE_PROXY = ISafe(0x74fEa3FB0eD030e9228026E7F413D66186d3D107);
2226
uint256 public constant SHORT_TX_EXECUTION_DELAY = 1 hours;

script/DeployCanonGuard.s.sol

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,24 @@ contract DeployCanonGuard is Constants, Script {
120120
}
121121

122122
function _deployAllChainsFactories() internal {
123-
canonGuardFactory = new CanonGuardFactory{salt: SALT}(address(MULTI_SEND_CALL_ONLY));
124-
allowanceClaimorFactory = new AllowanceClaimorFactory{salt: SALT}();
125-
preApproveActionFactory = new PreApproveActionFactory{salt: SALT}();
126-
cappedTokenTransfersHubFactory = new CappedTokenTransfersHubFactory{salt: SALT}();
127-
changeSafeGuardActionFactory = new ChangeSafeGuardActionFactory{salt: SALT}();
128-
setEmergencyCallerActionFactory = new SetEmergencyCallerActionFactory{salt: SALT}();
129-
setEmergencyTriggerActionFactory = new SetEmergencyTriggerActionFactory{salt: SALT}();
130-
simpleActionsFactory = new SimpleActionsFactory{salt: SALT}();
131-
simpleTransfersFactory = new SimpleTransfersFactory{salt: SALT}();
123+
canonGuardFactory = ICanonGuardFactory(CREATE_X.deployCreate2(SALT, type(CanonGuardFactory).creationCode));
124+
allowanceClaimorFactory =
125+
IAllowanceClaimorFactory(CREATE_X.deployCreate2(SALT, type(AllowanceClaimorFactory).creationCode));
126+
preApproveActionFactory =
127+
IPreApproveActionFactory(CREATE_X.deployCreate2(SALT, type(PreApproveActionFactory).creationCode));
128+
cappedTokenTransfersHubFactory =
129+
ICappedTokenTransfersHubFactory(CREATE_X.deployCreate2(SALT, type(CappedTokenTransfersHubFactory).creationCode));
130+
changeSafeGuardActionFactory =
131+
IChangeSafeGuardActionFactory(CREATE_X.deployCreate2(SALT, type(ChangeSafeGuardActionFactory).creationCode));
132+
setEmergencyCallerActionFactory = ISetEmergencyCallerActionFactory(
133+
CREATE_X.deployCreate2(SALT, type(SetEmergencyCallerActionFactory).creationCode)
134+
);
135+
setEmergencyTriggerActionFactory = ISetEmergencyTriggerActionFactory(
136+
CREATE_X.deployCreate2(SALT, type(SetEmergencyTriggerActionFactory).creationCode)
137+
);
138+
simpleActionsFactory = ISimpleActionsFactory(CREATE_X.deployCreate2(SALT, type(SimpleActionsFactory).creationCode));
139+
simpleTransfersFactory =
140+
ISimpleTransfersFactory(CREATE_X.deployCreate2(SALT, type(SimpleTransfersFactory).creationCode));
132141
}
133142

134143
function _deployEthereumFactories() internal {

src/contracts/CanonGuard.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ contract CanonGuard is OnlyCanonGuard, EmergencyModeHook, ICanonGuard {
8585
* @notice Constructor that sets up the Safe, MultiSendCallOnly, execution delays and default expiry delay
8686
* @param _parent The parent that deployed the CanonGuard contract
8787
* @param _safe The Gnosis Safe contract address
88-
* @param _multiSendCallOnly The MultiSendCallOnly contract address
88+
* @param _multiSendCallOnly The MultiSendCallOnly contract address. The list of compatible deployments can be found here:
89+
* https://github.com/safe-global/safe-deployments/blob/54bc801cd3513533fc5a8c6994ce461bc733812a/src/assets/v1.4.1/multi_send_call_only.json
8990
* @param _shortTxExecutionDelay The short transaction execution delay (in seconds)
9091
* @param _longTxExecutionDelay The long transaction execution delay (in seconds)
9192
* @param _txExpiryDelay The transaction expiry delay (in seconds after executable)

src/contracts/factories/CanonGuardFactory.sol

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ pragma solidity 0.8.30;
33

44
import {CanonGuard} from 'contracts/CanonGuard.sol';
55
import {Factory} from 'contracts/factories/Factory.sol';
6+
import {ICreateX} from 'interfaces/external/ICreateX.sol';
67
import {ICanonGuardFactory} from 'interfaces/factories/ICanonGuardFactory.sol';
7-
import {CREATE3} from 'solady/utils/CREATE3.sol';
88

99
/**
1010
* @title CanonGuardFactory
@@ -14,48 +14,42 @@ contract CanonGuardFactory is ICanonGuardFactory, Factory {
1414
// ~~~ STORAGE ~~~
1515

1616
/// @inheritdoc ICanonGuardFactory
17-
address public immutable MULTI_SEND_CALL_ONLY;
18-
19-
// ~~~ CONSTRUCTOR ~~~
20-
21-
/**
22-
* @notice Constructor that sets up the MultiSendCallOnly contract
23-
* @param _multiSendCallOnly The MultiSendCallOnly contract address
24-
*/
25-
constructor(address _multiSendCallOnly) {
26-
if (_multiSendCallOnly == address(0)) revert MultiSendCallOnlyCannotBeZero();
27-
28-
MULTI_SEND_CALL_ONLY = _multiSendCallOnly;
29-
}
17+
ICreateX public constant CREATE_X = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
3018

3119
// ~~~ FACTORY METHODS ~~~
3220

3321
/// @inheritdoc ICanonGuardFactory
3422
function createCanonGuard(
3523
address _safe,
24+
uint256 _nonce,
25+
address _multiSendCallOnly,
3626
uint256 _shortTxExecutionDelay,
3727
uint256 _longTxExecutionDelay,
3828
uint256 _txExpiryDelay,
3929
uint256 _maxApprovalDuration,
4030
address _emergencyTrigger,
4131
address _emergencyCaller
4232
) external returns (address _canonGuard) {
43-
_canonGuard = CREATE3.deployDeterministic(
33+
if (_safe != msg.sender) revert DeployerMustBeTheSafe();
34+
if (_multiSendCallOnly == address(0)) revert MultiSendCallOnlyCannotBeZero();
35+
36+
// Deploying using hash of the SAFE address as salt
37+
_canonGuard = CREATE_X.deployCreate3(
38+
keccak256(abi.encode(_safe, _nonce)),
4439
abi.encodePacked(
4540
type(CanonGuard).creationCode,
4641
abi.encode(
4742
address(this),
4843
_safe,
49-
MULTI_SEND_CALL_ONLY,
44+
_multiSendCallOnly,
5045
_shortTxExecutionDelay,
5146
_longTxExecutionDelay,
5247
_txExpiryDelay,
5348
_maxApprovalDuration,
5449
_emergencyTrigger,
5550
_emergencyCaller
5651
)
57-
),
58-
keccak256(abi.encode(_safe))
52+
)
5953
);
6054

6155
_children[_canonGuard] = true;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.30;
3+
4+
/**
5+
* @title ICreateX
6+
* @notice Interface for the CreateX contract
7+
*/
8+
interface ICreateX {
9+
/**
10+
* @notice Thrown when a contract creation fails
11+
* @param _emitter The address of the emitter
12+
*/
13+
error FailedContractCreation(address _emitter);
14+
15+
/**
16+
* @notice Deploys a new contract using CREATE3
17+
* @param _salt The salt for the deployment
18+
* @param _initCode The init code for the deployment
19+
* @return _newContract The address of the new contract
20+
*/
21+
function deployCreate3(bytes32 _salt, bytes memory _initCode) external payable returns (address _newContract);
22+
23+
/**
24+
* @notice Deploys a new contract using CREATE2
25+
* @param _salt The salt for the deployment
26+
* @param _initCode The init code for the deployment
27+
* @return _newContract The address of the new contract
28+
*/
29+
function deployCreate2(bytes32 _salt, bytes memory _initCode) external payable returns (address _newContract);
30+
31+
/**
32+
* @notice Computes the address of a contract deployed using CREATE3
33+
* @param _salt The salt for the deployment
34+
* @return _computedAddress The computed address
35+
*/
36+
function computeCreate3Address(bytes32 _salt) external pure returns (address _computedAddress);
37+
38+
/**
39+
* @notice Computes the address of a contract deployed using CREATE2
40+
* @param _salt The salt for the deployment
41+
* @param _initCodeHash The init code hash
42+
* @return _computedAddress The computed address
43+
*/
44+
function computeCreate2Address(bytes32 _salt, bytes32 _initCodeHash) external pure returns (address _computedAddress);
45+
}

src/interfaces/factories/ICanonGuardFactory.sol

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.30;
33

4+
import {ICreateX} from 'interfaces/external/ICreateX.sol';
45
import {IFactory} from 'interfaces/factories/IFactory.sol';
56

67
/**
@@ -37,11 +38,18 @@ interface ICanonGuardFactory is IFactory {
3738
*/
3839
error MultiSendCallOnlyCannotBeZero();
3940

41+
/**
42+
* @notice Thrown when the deployer is not the Safe contract
43+
*/
44+
error DeployerMustBeTheSafe();
45+
4046
// ~~~ FACTORY METHODS ~~~
4147

4248
/**
4349
* @notice Creates a CanonGuard contract
4450
* @param _safe The Gnosis Safe contract address
51+
* @param _nonce A nonce used to avoid collisions when redeploying the CanonGuard contract with the same Safe address
52+
* @param _multiSendCallOnly The MultiSendCallOnly contract address
4553
* @param _shortTxExecutionDelay The short transaction execution delay (in seconds)
4654
* @param _longTxExecutionDelay The long transaction execution delay (in seconds)
4755
* @param _txExpiryDelay The transaction expiry delay (in seconds after executable)
@@ -52,6 +60,8 @@ interface ICanonGuardFactory is IFactory {
5260
*/
5361
function createCanonGuard(
5462
address _safe,
63+
uint256 _nonce,
64+
address _multiSendCallOnly,
5565
uint256 _shortTxExecutionDelay,
5666
uint256 _longTxExecutionDelay,
5767
uint256 _txExpiryDelay,
@@ -63,8 +73,8 @@ interface ICanonGuardFactory is IFactory {
6373
// ~~~ STORAGE METHODS ~~~
6474

6575
/**
66-
* @notice Gets the MultiSendCallOnly contract
67-
* @return _multiSendCallOnly The MultiSendCallOnly contract address
76+
* @notice Gets the CreateX contract
77+
* @return _createX The CreateX contract address
6878
*/
69-
function MULTI_SEND_CALL_ONLY() external view returns (address _multiSendCallOnly);
79+
function CREATE_X() external view returns (ICreateX _createX);
7080
}

test/integration/BasicTest.t.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ contract IntegrationBasicTest is DeployCanonGuard, EthereumConstants, Test {
4949
run();
5050

5151
// Deploy the CanonGuard contract (overriding the dummy contract)
52+
vm.prank(address(_safeProxy));
5253
_canonGuard = ICanonGuard(
5354
canonGuardFactory.createCanonGuard(
5455
address(_safeProxy),
56+
0,
57+
address(MULTI_SEND_CALL_ONLY),
5558
SHORT_TX_EXECUTION_DELAY,
5659
LONG_TX_EXECUTION_DELAY,
5760
TX_EXPIRY_DELAY,

test/integration/ethereum/IntegrationCanonGuardManageActions.t.sol

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {IPreApproveAction} from 'interfaces/actions-builders/IPreApproveAction.s
88
import {ISetEmergencyCallerAction} from 'interfaces/actions-builders/ISetEmergencyCallerAction.sol';
99
import {ISetEmergencyTriggerAction} from 'interfaces/actions-builders/ISetEmergencyTriggerAction.sol';
1010
import {ISimpleActions} from 'interfaces/actions-builders/ISimpleActions.sol';
11+
import {ICreateX} from 'interfaces/external/ICreateX.sol';
1112
import {IntegrationEthereumBase} from 'test/integration/ethereum/IntegrationEthereumBase.sol';
1213

1314
contract IntegrationCanonGuardManageActions is IntegrationEthereumBase {
@@ -566,4 +567,50 @@ contract IntegrationCanonGuardManageActions is IntegrationEthereumBase {
566567
// Assert that the WETH has been collected
567568
assertEq(WETH.balanceOf(address(SAFE_PROXY)), _safeWETHBalanceBefore + 1 ether);
568569
}
570+
571+
function test_CanonGuardDeploymentNonce() public {
572+
vm.startPrank(address(SAFE_PROXY));
573+
574+
// Deploy the CanonGuard contract through the factory with a non-used nonce
575+
canonGuardFactory.createCanonGuard(
576+
address(SAFE_PROXY),
577+
1,
578+
address(MULTI_SEND_CALL_ONLY),
579+
SHORT_TX_EXECUTION_DELAY,
580+
LONG_TX_EXECUTION_DELAY,
581+
TX_EXPIRY_DELAY,
582+
MAX_APPROVAL_DURATION,
583+
makeAddr('emergencyTrigger'),
584+
makeAddr('emergencyCaller')
585+
);
586+
587+
// Re-deploy the CanonGuard contract through the factory with the same nonce, should revert
588+
vm.expectRevert(abi.encodeWithSelector(ICreateX.FailedContractCreation.selector, address(CREATE_X)));
589+
canonGuardFactory.createCanonGuard(
590+
address(SAFE_PROXY),
591+
1,
592+
address(MULTI_SEND_CALL_ONLY),
593+
SHORT_TX_EXECUTION_DELAY,
594+
LONG_TX_EXECUTION_DELAY,
595+
TX_EXPIRY_DELAY,
596+
MAX_APPROVAL_DURATION,
597+
makeAddr('emergencyTrigger'),
598+
makeAddr('emergencyCaller')
599+
);
600+
601+
// Deploy the CanonGuard contract through the factory with a new nonce
602+
canonGuardFactory.createCanonGuard(
603+
address(SAFE_PROXY),
604+
2,
605+
address(MULTI_SEND_CALL_ONLY),
606+
SHORT_TX_EXECUTION_DELAY,
607+
LONG_TX_EXECUTION_DELAY,
608+
TX_EXPIRY_DELAY,
609+
MAX_APPROVAL_DURATION,
610+
makeAddr('emergencyTrigger'),
611+
makeAddr('emergencyCaller')
612+
);
613+
614+
vm.stopPrank();
615+
}
569616
}

test/integration/ethereum/IntegrationEthereumBase.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ abstract contract IntegrationEthereumBase is DeployCanonGuard, EthereumConstants
4848
run();
4949

5050
// Deploy the CanonGuard contract
51+
vm.prank(address(SAFE_PROXY));
5152
canonGuard = ICanonGuard(
5253
canonGuardFactory.createCanonGuard(
5354
address(SAFE_PROXY),
55+
0,
56+
address(MULTI_SEND_CALL_ONLY),
5457
SHORT_TX_EXECUTION_DELAY,
5558
LONG_TX_EXECUTION_DELAY,
5659
TX_EXPIRY_DELAY,

0 commit comments

Comments
 (0)