Skip to content

Commit 709acc9

Browse files
committed
replace TransferSpeed enum with uint32 minFinalityThreshold, rename tokenMinter to tokenMinterV2, and implement Create2 CCTPv2 contract deployments
1 parent aaef78e commit 709acc9

File tree

11 files changed

+170
-58
lines changed

11 files changed

+170
-58
lines changed

.env-example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ EXPECTED_DEPOSIT_ERC4626_ACTION_ADDRESS=
5252
EXPECTED_SWEEP_DEPOSIT_ERC4626_ACTION_ADDRESS=
5353
EXPECTED_WITHDRAW_ERC4626_ACTION_ADDRESS=
5454
EXPECTED_SWEEP_WITHDRAW_ERC4626_ACTION_ADDRESS=
55+
EXPECTED_SWEEP_CCTP_V2_ACTION_ADDRESS=
56+
EXPECTED_TRANSFER_CCTP_V2_ACTION_ADDRESS=
5557

5658

5759
UNIVERSAL_ROUTER_ADDRESS=

script/deployment/actions/DeploySweepCCTPV2Action.s.sol

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,96 @@ import {SweepCCTPV2Action} from "../../../src/actions/SweepCCTPV2Action.sol";
99
contract DeploySweepCCTPV2Action is Script {
1010
/// @dev make sure to run `cp .env_example .env` and fill in each variable
1111
/// then run `source .env` in your terminal before copying and pasting one of the commands below
12-
// commands to deploy:
12+
/// @dev this script will deploy to the same address on every chain.
13+
/// this deterministic address depend on a few things:
14+
/// - the owner address
15+
/// - the salt
16+
/// - the creation code of the contract
17+
/// - **** the number of optimizer_runs will change the creation code (see foundry.toml) ****
18+
/// - **** the version of the Solidity compiler will change the creation code ****
19+
/// - **** the EVM version (cancun, prague, etc) will change the creation code ****
20+
/// - **** dependency versions can change the creation code ****
21+
/// - **** the forge version can change the creation code ****
22+
/// - **** compiler flags (--via-ir, --overwrite, etc) can change the creation code ****
23+
/// - the address of the deployer (this won't change because we are using the cannoical Create2 factory 0x4e59b44847b379578588920ca78fbf26c0b4956c, but good to know)
24+
///
25+
///
26+
/// if any of these values change, the addresses will change, so we must be careful to keep these values constant.
27+
/// in order to help with this, a check is added here to ensure that the calculated address matches the expected address
28+
/// before deploying. if the addresses do not match, the script will revert.
29+
// command to generate the expected deployment address (without actually deploying):
30+
//
31+
// forge script DeploySweepCCTPV2Action
32+
33+
// commands to deterministically deploy (and check the expected address before deploying):
1334
//
1435
// - with private key (on Anvil): forge script DeploySweepCCTPV2Action --broadcast --fork-url http://localhost:8545 --private-key $ANVIL_DEPLOYER_PK
1536
// - with private key: forge script DeploySweepCCTPV2Action --broadcast --rpc-url $RPC_URL --private-key $DEPLOYER_PK
1637
// - with Ledger: forge script DeploySweepCCTPV2Action --broadcast --rpc-url $RPC_URL --ledger
1738
// - with AWS: forge script DeploySweepCCTPV2Action --broadcast --rpc-url $RPC_URL --aws
1839

40+
bytes32 constant salt = keccak256("ON_TIME_INSTRUCTED_MONEY");
41+
42+
error ExpectedAddressMismatch();
43+
1944
function run() public {
2045
address tokenMessengerV2Address = vm.envAddress("CCTP_V2_TOKEN_MESSENGER_ADDRESS");
21-
address tokenMinterAddress = vm.envAddress("CCTP_V2_TOKEN_MINTER_ADDRESS");
46+
address tokenMinterV2Address = vm.envAddress("CCTP_V2_TOKEN_MINTER_ADDRESS");
2247
address feeTokenRegistryAddress = vm.envAddress("EXPECTED_FEE_TOKEN_REGISTRY_ADDRESS");
2348
address treasuryAddress = vm.envAddress("EXPECTED_TREASURY_ADDRESS");
2449
uint256 gasConstant = vm.envUint("SWEEP_CCTP_V2_ACTION_GAS_CONSTANT");
2550

51+
// if this isn't a dry-run (aka we're using `--broadcast`), make sure to check the expected address
52+
if (vm.isContext(VmSafe.ForgeContext.ScriptBroadcast)) {
53+
checkExpectedAddress(
54+
tokenMessengerV2Address, tokenMinterV2Address, feeTokenRegistryAddress, treasuryAddress, gasConstant
55+
);
56+
}
57+
2658
vm.startBroadcast();
2759

28-
// deploy SweepCCTPV2Action
29-
SweepCCTPV2Action sweepCCTPV2Action = new SweepCCTPV2Action(
30-
tokenMessengerV2Address, tokenMinterAddress, feeTokenRegistryAddress, treasuryAddress, gasConstant
60+
// deterministically deploy SweepCCTPV2Action via canonical Create2 deployer
61+
SweepCCTPV2Action sweepCCTPV2Action = new SweepCCTPV2Action{salt: salt}(
62+
tokenMessengerV2Address, tokenMinterV2Address, feeTokenRegistryAddress, treasuryAddress, gasConstant
3163
);
3264

3365
vm.stopBroadcast();
3466

3567
console2.log("SweepCCTPV2Action deployed at:", address(sweepCCTPV2Action));
3668
}
69+
70+
function checkExpectedAddress(
71+
address tokenMessengerV2Address,
72+
address tokenMinterV2Address,
73+
address feeTokenRegistryAddress,
74+
address treasuryAddress,
75+
uint256 gasConstant
76+
) public view {
77+
/// @dev before deploying for the first time, generate this expected address by running this script in dry-run mode (see above).
78+
/// once it has been deployed for the first time, that deployed address should be used as the expected address from then on.
79+
address expectedAddress = vm.envAddress("EXPECTED_SWEEP_CCTP_V2_ACTION_ADDRESS");
80+
81+
// calculate the expected address using the current init code
82+
address calculatedAddress = vm.computeCreate2Address(
83+
salt,
84+
keccak256(
85+
abi.encodePacked(
86+
type(SweepCCTPV2Action).creationCode,
87+
abi.encode(
88+
tokenMessengerV2Address,
89+
tokenMinterV2Address,
90+
feeTokenRegistryAddress,
91+
treasuryAddress,
92+
gasConstant
93+
)
94+
)
95+
)
96+
);
97+
98+
// revert if the expected address does not match the calculated address
99+
if (expectedAddress != calculatedAddress) {
100+
revert ExpectedAddressMismatch();
101+
}
102+
}
37103
}
38104

script/deployment/actions/DeployTransferCCTPV2Action.s.sol

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,96 @@ import {TransferCCTPV2Action} from "../../../src/actions/TransferCCTPV2Action.so
99
contract DeployTransferCCTPV2Action is Script {
1010
/// @dev make sure to run `cp .env_example .env` and fill in each variable
1111
/// then run `source .env` in your terminal before copying and pasting one of the commands below
12-
// commands to deploy:
12+
/// @dev this script will deploy to the same address on every chain.
13+
/// this deterministic address depend on a few things:
14+
/// - the owner address
15+
/// - the salt
16+
/// - the creation code of the contract
17+
/// - **** the number of optimizer_runs will change the creation code (see foundry.toml) ****
18+
/// - **** the version of the Solidity compiler will change the creation code ****
19+
/// - **** the EVM version (cancun, prague, etc) will change the creation code ****
20+
/// - **** dependency versions can change the creation code ****
21+
/// - **** the forge version can change the creation code ****
22+
/// - **** compiler flags (--via-ir, --overwrite, etc) can change the creation code ****
23+
/// - the address of the deployer (this won't change because we are using the cannoical Create2 factory 0x4e59b44847b379578588920ca78fbf26c0b4956c, but good to know)
24+
///
25+
///
26+
/// if any of these values change, the addresses will change, so we must be careful to keep these values constant.
27+
/// in order to help with this, a check is added here to ensure that the calculated address matches the expected address
28+
/// before deploying. if the addresses do not match, the script will revert.
29+
// command to generate the expected deployment address (without actually deploying):
30+
//
31+
// forge script DeployTransferCCTPV2Action
32+
33+
// commands to deterministically deploy (and check the expected address before deploying):
1334
//
1435
// - with private key (on Anvil): forge script DeployTransferCCTPV2Action --broadcast --fork-url http://localhost:8545 --private-key $ANVIL_DEPLOYER_PK
1536
// - with private key: forge script DeployTransferCCTPV2Action --broadcast --rpc-url $RPC_URL --private-key $DEPLOYER_PK
1637
// - with Ledger: forge script DeployTransferCCTPV2Action --broadcast --rpc-url $RPC_URL --ledger
1738
// - with AWS: forge script DeployTransferCCTPV2Action --broadcast --rpc-url $RPC_URL --aws
1839

40+
bytes32 constant salt = keccak256("ON_TIME_INSTRUCTED_MONEY");
41+
42+
error ExpectedAddressMismatch();
43+
1944
function run() public {
2045
address tokenMessengerV2Address = vm.envAddress("CCTP_V2_TOKEN_MESSENGER_ADDRESS");
21-
address tokenMinterAddress = vm.envAddress("CCTP_V2_TOKEN_MINTER_ADDRESS");
46+
address tokenMinterV2Address = vm.envAddress("CCTP_V2_TOKEN_MINTER_ADDRESS");
2247
address feeTokenRegistryAddress = vm.envAddress("EXPECTED_FEE_TOKEN_REGISTRY_ADDRESS");
2348
address treasuryAddress = vm.envAddress("EXPECTED_TREASURY_ADDRESS");
2449
uint256 gasConstant = vm.envUint("TRANSFER_CCTP_V2_ACTION_GAS_CONSTANT");
2550

51+
// if this isn't a dry-run (aka we're using `--broadcast`), make sure to check the expected address
52+
if (vm.isContext(VmSafe.ForgeContext.ScriptBroadcast)) {
53+
checkExpectedAddress(
54+
tokenMessengerV2Address, tokenMinterV2Address, feeTokenRegistryAddress, treasuryAddress, gasConstant
55+
);
56+
}
57+
2658
vm.startBroadcast();
2759

28-
// deploy TransferCCTPV2Action
29-
TransferCCTPV2Action transferCCTPV2Action = new TransferCCTPV2Action(
30-
tokenMessengerV2Address, tokenMinterAddress, feeTokenRegistryAddress, treasuryAddress, gasConstant
60+
// deterministically deploy TransferCCTPV2Action via canonical Create2 deployer
61+
TransferCCTPV2Action transferCCTPV2Action = new TransferCCTPV2Action{salt: salt}(
62+
tokenMessengerV2Address, tokenMinterV2Address, feeTokenRegistryAddress, treasuryAddress, gasConstant
3163
);
3264

3365
vm.stopBroadcast();
3466

3567
console2.log("TransferCCTPV2Action deployed at:", address(transferCCTPV2Action));
3668
}
69+
70+
function checkExpectedAddress(
71+
address tokenMessengerV2Address,
72+
address tokenMinterV2Address,
73+
address feeTokenRegistryAddress,
74+
address treasuryAddress,
75+
uint256 gasConstant
76+
) public view {
77+
/// @dev before deploying for the first time, generate this expected address by running this script in dry-run mode (see above).
78+
/// once it has been deployed for the first time, that deployed address should be used as the expected address from then on.
79+
address expectedAddress = vm.envAddress("EXPECTED_TRANSFER_CCTP_V2_ACTION_ADDRESS");
80+
81+
// calculate the expected address using the current init code
82+
address calculatedAddress = vm.computeCreate2Address(
83+
salt,
84+
keccak256(
85+
abi.encodePacked(
86+
type(TransferCCTPV2Action).creationCode,
87+
abi.encode(
88+
tokenMessengerV2Address,
89+
tokenMinterV2Address,
90+
feeTokenRegistryAddress,
91+
treasuryAddress,
92+
gasConstant
93+
)
94+
)
95+
)
96+
);
97+
98+
// revert if the expected address does not match the calculated address
99+
if (expectedAddress != calculatedAddress) {
100+
revert ExpectedAddressMismatch();
101+
}
102+
}
37103
}
38104

src/actions/SweepCCTPV2Action.sol

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
2323

2424
/// @notice the CCTP V2 TokenMessenger contract
2525
ITokenMessengerV2 public immutable tokenMessengerV2;
26-
/// @notice the CCTP TokenMinter contract
26+
/// @notice the CCTP V2 TokenMinter contract
2727
/// @dev the TokenMinter contract implements the ITokenController interface
28-
ITokenController public immutable tokenMinter;
28+
ITokenController public immutable tokenMinterV2;
2929

3030
constructor(
3131
address tokenMessengerV2Address,
@@ -35,7 +35,7 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
3535
uint256 gasConstant_
3636
) OtimFee(feeTokenRegistryAddress, treasuryAddress, gasConstant_) {
3737
tokenMessengerV2 = ITokenMessengerV2(tokenMessengerV2Address);
38-
tokenMinter = ITokenController(tokenMinterAddress);
38+
tokenMinterV2 = ITokenController(tokenMinterAddress);
3939
}
4040

4141
/// @inheritdoc IAction
@@ -55,7 +55,7 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
5555
arguments.endBalance,
5656
arguments.destinationCaller,
5757
arguments.maxFee,
58-
arguments.transferSpeed,
58+
arguments.minFinalityThreshold,
5959
hash(arguments.fee)
6060
)
6161
);
@@ -95,7 +95,7 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
9595
}
9696

9797
// get the CCTP burnLimitPerMessage for the token
98-
uint256 burnLimitPerMessage = tokenMinter.burnLimitsPerMessage(arguments.token);
98+
uint256 burnLimitPerMessage = tokenMinterV2.burnLimitsPerMessage(arguments.token);
9999

100100
// if the burnLimitPerMessage is zero, the token is not supported
101101
if (burnLimitPerMessage == 0) {
@@ -116,9 +116,6 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
116116
// slither-disable-next-line unused-return
117117
IERC20(arguments.token).approve(address(tokenMessengerV2), transferAmount);
118118

119-
// convert transfer speed to finality threshold
120-
uint32 minFinalityThreshold = arguments.transferSpeed == TransferSpeed.FAST ? 1000 : 2000;
121-
122119
// initiate CCTP V2 transfer
123120
tokenMessengerV2.depositForBurn(
124121
transferAmount,
@@ -127,7 +124,7 @@ contract SweepCCTPV2Action is IAction, ISweepCCTPV2Action, OtimFee {
127124
arguments.token,
128125
arguments.destinationCaller,
129126
arguments.maxFee,
130-
minFinalityThreshold
127+
arguments.minFinalityThreshold
131128
);
132129

133130
// charge the fee

src/actions/TransferCCTPV2Action.sol

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
2424

2525
/// @notice the CCTP V2 TokenMessenger contract
2626
ITokenMessengerV2 public immutable tokenMessengerV2;
27-
/// @notice the CCTP TokenMinter contract
27+
/// @notice the CCTP V2 TokenMinter contract
2828
/// @dev the TokenMinter contract implements the ITokenController interface
29-
ITokenController public immutable tokenMinter;
29+
ITokenController public immutable tokenMinterV2;
3030

3131
constructor(
3232
address tokenMessengerV2Address,
@@ -36,7 +36,7 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
3636
uint256 gasConstant_
3737
) OtimFee(feeTokenRegistryAddress, treasuryAddress, gasConstant_) {
3838
tokenMessengerV2 = ITokenMessengerV2(tokenMessengerV2Address);
39-
tokenMinter = ITokenController(tokenMinterAddress);
39+
tokenMinterV2 = ITokenController(tokenMinterAddress);
4040
}
4141

4242
/// @inheritdoc IAction
@@ -55,7 +55,7 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
5555
arguments.destinationMintRecipient,
5656
arguments.destinationCaller,
5757
arguments.maxFee,
58-
arguments.transferSpeed,
58+
arguments.minFinalityThreshold,
5959
hash(arguments.schedule),
6060
hash(arguments.fee)
6161
)
@@ -97,7 +97,7 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
9797
}
9898

9999
// get the CCTP burnLimitPerMessage for the token
100-
uint256 burnLimitPerMessage = tokenMinter.burnLimitsPerMessage(arguments.token);
100+
uint256 burnLimitPerMessage = tokenMinterV2.burnLimitsPerMessage(arguments.token);
101101

102102
// if the burnLimitPerMessage is zero, the token is not supported
103103
if (burnLimitPerMessage == 0) {
@@ -118,9 +118,6 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
118118
// slither-disable-next-line unused-return
119119
IERC20(arguments.token).approve(address(tokenMessengerV2), transferAmount);
120120

121-
// convert transfer speed to finality threshold
122-
uint32 minFinalityThreshold = arguments.transferSpeed == TransferSpeed.FAST ? 1000 : 2000;
123-
124121
// initiate CCTP V2 transfer
125122
tokenMessengerV2.depositForBurn(
126123
transferAmount,
@@ -129,7 +126,7 @@ contract TransferCCTPV2Action is IAction, ITransferCCTPV2Action, Interval, OtimF
129126
arguments.token,
130127
arguments.destinationCaller,
131128
arguments.maxFee,
132-
minFinalityThreshold
129+
arguments.minFinalityThreshold
133130
);
134131

135132
// charge the fee

src/actions/interfaces/ISweepCCTPV2Action.sol

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,17 @@ pragma solidity ^0.8.26;
44
import {IOtimFee} from "../fee-models/interfaces/IOtimFee.sol";
55

66
bytes32 constant INSTRUCTION_TYPEHASH = keccak256(
7-
"Instruction(uint256 salt,uint256 maxExecutions,address action,SweepCCTPV2 sweepCCTPV2)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)SweepCCTPV2(address token,uint32 destinationDomain,bytes32 destinationMintRecipient,uint256 threshold,uint256 endBalance,bytes32 destinationCaller,uint256 maxFee,uint8 transferSpeed,Fee fee)"
7+
"Instruction(uint256 salt,uint256 maxExecutions,address action,SweepCCTPV2 sweepCCTPV2)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)SweepCCTPV2(address token,uint32 destinationDomain,bytes32 destinationMintRecipient,uint256 threshold,uint256 endBalance,bytes32 destinationCaller,uint256 maxFee,uint32 minFinalityThreshold,Fee fee)"
88
);
99

1010
bytes32 constant ARGUMENTS_TYPEHASH = keccak256(
11-
"SweepCCTPV2(address token,uint32 destinationDomain,bytes32 destinationMintRecipient,uint256 threshold,uint256 endBalance,bytes32 destinationCaller,uint256 maxFee,uint8 transferSpeed,Fee fee)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)"
11+
"SweepCCTPV2(address token,uint32 destinationDomain,bytes32 destinationMintRecipient,uint256 threshold,uint256 endBalance,bytes32 destinationCaller,uint256 maxFee,uint32 minFinalityThreshold,Fee fee)Fee(address token,uint256 maxBaseFeePerGas,uint256 maxPriorityFeePerGas,uint256 executionFee)"
1212
);
1313

1414
/// @title ISweepCCTPV2Action
1515
/// @author Otim Labs, Inc.
1616
/// @notice interface for SweepCCTPV2Action contract
1717
interface ISweepCCTPV2Action is IOtimFee {
18-
/// @notice transfer speed for CCTP V2 transfers
19-
/// @param FAST - fast transfer with lower finality (1000)
20-
/// @param STANDARD - standard transfer with higher finality (2000)
21-
enum TransferSpeed {
22-
FAST,
23-
STANDARD
24-
}
25-
2618
/// @notice arguments for SweepCCTPV2Action contract
2719
/// @param token - the token to sweep
2820
/// @param destinationDomain - the destination domain for the CCTP transfer
@@ -31,7 +23,7 @@ interface ISweepCCTPV2Action is IOtimFee {
3123
/// @param endBalance - the account's balance after the sweep
3224
/// @param destinationCaller - the address allowed to call receiveMessage on destination (bytes32(0) for anyone)
3325
/// @param maxFee - the maximum fee for the transfer in burn token units
34-
/// @param transferSpeed - the transfer speed (FAST or STANDARD)
26+
/// @param minFinalityThreshold - minimum finality threshold (e.g., 1000=fast, 2000=standard)
3527
/// @param fee - the fee to be paid
3628
struct SweepCCTPV2 {
3729
address token;
@@ -41,7 +33,7 @@ interface ISweepCCTPV2Action is IOtimFee {
4133
uint256 endBalance;
4234
bytes32 destinationCaller;
4335
uint256 maxFee;
44-
TransferSpeed transferSpeed;
36+
uint32 minFinalityThreshold;
4537
Fee fee;
4638
}
4739

0 commit comments

Comments
 (0)