@across-protocol/contracts 5.0.18 → 5.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/contracts/interfaces/ICounterfactualBeacon.sol +91 -0
  2. package/contracts/interfaces/ICounterfactualDeposit.sol +21 -0
  3. package/contracts/libraries/TronClones.sol +13 -0
  4. package/contracts/periphery/counterfactual/CounterfactualBeacon.sol +115 -0
  5. package/contracts/periphery/counterfactual/CounterfactualBeaconBase.sol +121 -0
  6. package/contracts/periphery/counterfactual/CounterfactualBeaconBootstrap.sol +39 -0
  7. package/contracts/periphery/counterfactual/CounterfactualDeposit.sol +109 -28
  8. package/contracts/periphery/counterfactual/CounterfactualDepositCCTP.sol +102 -57
  9. package/contracts/periphery/counterfactual/CounterfactualDepositFactory.sol +61 -83
  10. package/contracts/periphery/counterfactual/CounterfactualDepositFactoryTron.sol +14 -24
  11. package/contracts/periphery/counterfactual/CounterfactualDepositOFT.sol +119 -58
  12. package/contracts/periphery/counterfactual/CounterfactualDepositSpokePool.sol +122 -90
  13. package/contracts/periphery/counterfactual/CounterfactualDepositSpokePoolTr.sol +10 -21
  14. package/contracts/periphery/counterfactual/CounterfactualDepositVanillaCCTP.sol +170 -0
  15. package/contracts/periphery/counterfactual/CounterfactualImplementationBase.sol +51 -0
  16. package/dist/broadcast/deployed-addresses.json +6 -6
  17. package/dist/evm/artifacts/AdminWithdrawManager.sol/AdminWithdrawManager.json +1 -1
  18. package/dist/evm/artifacts/CounterfactualBeacon.sol/CounterfactualBeacon.json +1 -0
  19. package/dist/evm/artifacts/CounterfactualBeaconBase.sol/CounterfactualBeaconBase.json +1 -0
  20. package/dist/evm/artifacts/CounterfactualBeaconBase.sol/IBeaconTarget.json +1 -0
  21. package/dist/evm/artifacts/CounterfactualBeaconBootstrap.sol/CounterfactualBeaconBootstrap.json +1 -0
  22. package/dist/evm/artifacts/CounterfactualDeposit.sol/CounterfactualDeposit.json +1 -1
  23. package/dist/evm/artifacts/CounterfactualDepositCCTP.sol/CounterfactualDepositCCTP.json +1 -1
  24. package/dist/evm/artifacts/CounterfactualDepositFactory.sol/CounterfactualDepositFactory.json +1 -1
  25. package/dist/evm/artifacts/CounterfactualDepositFactoryTron.sol/CounterfactualDepositFactoryTron.json +1 -1
  26. package/dist/evm/artifacts/CounterfactualDepositOFT.sol/CounterfactualDepositOFT.json +1 -1
  27. package/dist/evm/artifacts/CounterfactualDepositOFT.sol/ISponsoredOFTSrcPeriphery.json +1 -1
  28. package/dist/evm/artifacts/CounterfactualDepositSpokePool.sol/CounterfactualDepositSpokePool.json +1 -1
  29. package/dist/evm/artifacts/CounterfactualDepositSpokePoolTr.sol/CounterfactualDepositSpokePoolTr.json +1 -1
  30. package/dist/evm/artifacts/CounterfactualDepositVanillaCCTP.sol/CounterfactualDepositVanillaCCTP.json +1 -0
  31. package/dist/evm/artifacts/CounterfactualImplementationBase.sol/CounterfactualImplementationBase.json +1 -0
  32. package/dist/evm/artifacts/CounterfactualTestBase.sol/CounterfactualTestBase.json +1 -0
  33. package/dist/evm/artifacts/ICounterfactualBeacon.sol/ICounterfactualBeacon.json +1 -0
  34. package/dist/evm/artifacts/ICounterfactualDeposit.sol/ICounterfactualDeposit.json +1 -1
  35. package/dist/evm/artifacts/Ownable2StepUpgradeable.sol/Ownable2StepUpgradeable.json +1 -0
  36. package/dist/evm/artifacts/OwnableUpgradeable.sol/OwnableUpgradeable.json +1 -1
  37. package/dist/evm/artifacts/UUPSUpgradeable.sol/UUPSUpgradeable.json +1 -1
  38. package/package.json +1 -1
  39. package/dist/evm/artifacts/Clones.sol/Clones.json +0 -1
@@ -3,25 +3,26 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
5
  import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
+ import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
7
+ import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
6
8
  import { SponsoredCCTPInterface } from "../../interfaces/SponsoredCCTPInterface.sol";
7
9
  import { ICounterfactualImplementation } from "../../interfaces/ICounterfactualImplementation.sol";
10
+ import { CounterfactualImplementationBase } from "./CounterfactualImplementationBase.sol";
8
11
  import { BPS_SCALAR } from "./CounterfactualConstants.sol";
9
12
 
10
- /**
11
- * @notice Minimal interface for calling depositForBurn on SponsoredCCTPSrcPeriphery
12
- * @custom:security-contact bugs@across.to
13
- */
13
+ /// @notice Minimal interface for `depositForBurn` on SponsoredCCTPSrcPeriphery.
14
14
  interface ISponsoredCCTPSrcPeriphery {
15
15
  function depositForBurn(SponsoredCCTPInterface.SponsoredCCTPQuote memory quote, bytes memory signature) external;
16
16
  }
17
17
 
18
18
  /**
19
- * @notice Route parameters committed to in the merkle leaf.
19
+ * @notice Route parameters committed to in the merkle leaf (chain-agnostic: no source chain, no token).
20
+ * @dev Burn token is always USDC (`beacon.usdc()`); source domain and periphery come from
21
+ * `beacon.cctpSourceDomain()` / `beacon.cctpSrcPeriphery()`.
20
22
  */
21
- struct CCTPDepositParams {
23
+ struct CCTPRouteParams {
22
24
  uint32 destinationDomain;
23
25
  bytes32 mintRecipient;
24
- bytes32 burnToken;
25
26
  bytes32 destinationCaller;
26
27
  uint256 cctpMaxFeeBps;
27
28
  uint32 minFinalityThreshold;
@@ -33,7 +34,9 @@ struct CCTPDepositParams {
33
34
  uint8 accountCreationMode;
34
35
  uint8 executionMode;
35
36
  bytes actionData;
36
- uint256 executionFee;
37
+ /// @dev Selector of the beacon getter for this route's per-chain execution-fee cap (e.g.
38
+ /// `beacon.usdcCctpMaxExecutionFee.selector`).
39
+ bytes4 maxExecutionFeeGetter;
37
40
  }
38
41
 
39
42
  /**
@@ -44,16 +47,20 @@ struct CCTPSubmitterData {
44
47
  address executionFeeRecipient;
45
48
  bytes32 nonce;
46
49
  uint256 cctpDeadline;
47
- bytes signature;
50
+ uint256 executionFee;
51
+ uint32 signatureDeadline;
52
+ bytes peripherySignature;
53
+ bytes counterfactualSignature;
48
54
  }
49
55
 
50
56
  /**
51
57
  * @title CounterfactualDepositCCTP
52
- * @notice Implementation contract for counterfactual deposits via SponsoredCCTP.
53
- * @dev Called via delegatecall from the CounterfactualDeposit dispatcher.
58
+ * @notice Counterfactual deposit via SponsoredCCTP.
59
+ * @dev Delegatecalled by the dispatcher. Periphery, source domain, burn token (USDC) and fee signer come
60
+ * from the beacon, so the impl holds no chain-specific values and has one address per chain.
54
61
  * @custom:security-contact bugs@across.to
55
62
  */
56
- contract CounterfactualDepositCCTP is ICounterfactualImplementation {
63
+ contract CounterfactualDepositCCTP is CounterfactualImplementationBase, EIP712 {
57
64
  using SafeERC20 for IERC20;
58
65
 
59
66
  /**
@@ -62,77 +69,115 @@ contract CounterfactualDepositCCTP is ICounterfactualImplementation {
62
69
  * @param executionFeeRecipient Address that received the execution fee.
63
70
  * @param nonce CCTP nonce used for the deposit.
64
71
  * @param cctpDeadline Deadline timestamp for the CCTP quote.
72
+ * @param executionFee Execution fee paid to the executor (in input token).
65
73
  */
66
74
  event CCTPDepositExecuted(
67
75
  uint256 amount,
68
76
  address indexed executionFeeRecipient,
69
77
  bytes32 nonce,
70
- uint256 cctpDeadline
78
+ uint256 cctpDeadline,
79
+ uint256 executionFee
71
80
  );
72
81
 
73
- /// @notice SponsoredCCTPSrcPeriphery contract (immutable, same for all deposits on this chain)
74
- address public immutable srcPeriphery;
82
+ error InvalidSignature();
83
+ error SignatureExpired();
84
+ error MaxExecutionFee();
75
85
 
76
- /// @notice CCTP source domain ID for this chain
77
- uint32 public immutable sourceDomain;
86
+ /// @notice EIP-712 typehash binding the local fee signature to the route, nonce, runtime fee, and deadline.
87
+ bytes32 public constant EXECUTE_CCTP_TYPEHASH =
88
+ keccak256("ExecuteCCTP(bytes32 routeParamsHash,bytes32 nonce,uint256 executionFee,uint32 signatureDeadline)");
78
89
 
79
- constructor(address _srcPeriphery, uint32 _sourceDomain) {
80
- srcPeriphery = _srcPeriphery;
81
- sourceDomain = _sourceDomain;
82
- }
90
+ constructor() EIP712("CounterfactualDepositCCTP", "v2.0.0") {}
83
91
 
84
92
  /**
85
93
  * @inheritdoc ICounterfactualImplementation
86
- * @dev Bridges tokens via SponsoredCCTP. `params` is ABI-encoded as `CCTPDepositParams`;
87
- * `submitterData` as `CCTPSubmitterData` (includes a signature forwarded to the CCTP periphery).
88
- * ERC-20 only (no native tokens). No local signature verification delegated to `srcPeriphery`.
94
+ * @dev Bridges tokens via SponsoredCCTP. `routeParamsEncoded` is ABI-encoded as `CCTPRouteParams`;
95
+ * `submitterDataEncoded` as `CCTPSubmitterData` (includes a signature forwarded to the CCTP periphery).
96
+ * ERC-20 (USDC) only. The local fee signature binds the route (`routeParamsHash`); `amount` is
97
+ * bound transitively via the periphery quote signature forwarded to `srcPeriphery`.
89
98
  */
90
- function execute(bytes calldata params, bytes calldata submitterData) external payable {
91
- CCTPDepositParams memory dp = abi.decode(params, (CCTPDepositParams));
92
- CCTPSubmitterData memory sd = abi.decode(submitterData, (CCTPSubmitterData));
99
+ function execute(bytes calldata routeParamsEncoded, bytes calldata submitterDataEncoded) external payable {
100
+ CCTPRouteParams memory routeParams = abi.decode(routeParamsEncoded, (CCTPRouteParams));
101
+ CCTPSubmitterData memory submitterData = abi.decode(submitterDataEncoded, (CCTPSubmitterData));
102
+
103
+ _verifySignature(keccak256(routeParamsEncoded), submitterData);
104
+ if (submitterData.executionFee > _resolveBeaconUint(routeParams.maxExecutionFeeGetter))
105
+ revert MaxExecutionFee();
93
106
 
94
- address inputToken = address(uint160(uint256(dp.burnToken)));
107
+ address srcPeriphery = _requireConfigured(_beacon().cctpSrcPeriphery());
108
+ address inputToken = _requireConfigured(_beacon().usdc());
95
109
 
96
- if (dp.executionFee > 0) IERC20(inputToken).safeTransfer(sd.executionFeeRecipient, dp.executionFee);
110
+ // Fee paid before the periphery call (load-bearing): the local signature binds the route and
111
+ // (nonce, fee, deadline) but not `amount`, so amount-replay protection is the periphery's nonce
112
+ // check — a replayed fee reverts at `depositForBurn` and rolls back this transfer.
113
+ if (submitterData.executionFee > 0)
114
+ IERC20(inputToken).safeTransfer(submitterData.executionFeeRecipient, submitterData.executionFee);
97
115
 
98
- uint256 depositAmount = sd.amount - dp.executionFee;
116
+ uint256 depositAmount = submitterData.amount - submitterData.executionFee;
99
117
 
100
118
  IERC20(inputToken).forceApprove(srcPeriphery, depositAmount);
101
119
 
102
- _depositForBurn(dp, sd, depositAmount);
120
+ _depositForBurn(srcPeriphery, inputToken, routeParams, submitterData, depositAmount);
103
121
 
104
- emit CCTPDepositExecuted(sd.amount, sd.executionFeeRecipient, sd.nonce, sd.cctpDeadline);
122
+ emit CCTPDepositExecuted(
123
+ submitterData.amount,
124
+ submitterData.executionFeeRecipient,
125
+ submitterData.nonce,
126
+ submitterData.cctpDeadline,
127
+ submitterData.executionFee
128
+ );
105
129
  }
106
130
 
107
- /**
108
- * @notice Calls depositForBurn on the SponsoredCCTPSrcPeriphery with the constructed quote.
109
- * @param dp Route parameters from the merkle leaf.
110
- * @param sd Submitter-provided execution data.
111
- * @param depositAmount Amount to deposit after deducting the execution fee.
112
- */
113
- function _depositForBurn(CCTPDepositParams memory dp, CCTPSubmitterData memory sd, uint256 depositAmount) private {
131
+ function _verifySignature(bytes32 routeParamsHash, CCTPSubmitterData memory submitterData) private view {
132
+ if (block.timestamp > submitterData.signatureDeadline) revert SignatureExpired();
133
+ bytes32 structHash = keccak256(
134
+ abi.encode(
135
+ EXECUTE_CCTP_TYPEHASH,
136
+ routeParamsHash,
137
+ submitterData.nonce,
138
+ submitterData.executionFee,
139
+ submitterData.signatureDeadline
140
+ )
141
+ );
142
+ if (ECDSA.recover(_hashTypedDataV4(structHash), submitterData.counterfactualSignature) != _beacon().signer())
143
+ revert InvalidSignature();
144
+ }
145
+
146
+ /// @notice Calls `depositForBurn` on the SponsoredCCTPSrcPeriphery with the constructed quote.
147
+ /// @param srcPeriphery The CCTP source periphery (from the beacon).
148
+ /// @param inputToken The burn token, USDC (from the beacon).
149
+ /// @param routeParams Route parameters from the merkle leaf.
150
+ /// @param submitterData Submitter-provided execution data.
151
+ /// @param depositAmount Amount to deposit after deducting the execution fee.
152
+ function _depositForBurn(
153
+ address srcPeriphery,
154
+ address inputToken,
155
+ CCTPRouteParams memory routeParams,
156
+ CCTPSubmitterData memory submitterData,
157
+ uint256 depositAmount
158
+ ) private {
114
159
  ISponsoredCCTPSrcPeriphery(srcPeriphery).depositForBurn(
115
160
  SponsoredCCTPInterface.SponsoredCCTPQuote({
116
- sourceDomain: sourceDomain,
117
- destinationDomain: dp.destinationDomain,
118
- mintRecipient: dp.mintRecipient,
161
+ sourceDomain: _beacon().cctpSourceDomain(),
162
+ destinationDomain: routeParams.destinationDomain,
163
+ mintRecipient: routeParams.mintRecipient,
119
164
  amount: depositAmount,
120
- burnToken: dp.burnToken,
121
- destinationCaller: dp.destinationCaller,
122
- maxFee: (depositAmount * dp.cctpMaxFeeBps) / BPS_SCALAR,
123
- minFinalityThreshold: dp.minFinalityThreshold,
124
- nonce: sd.nonce,
125
- deadline: sd.cctpDeadline,
126
- maxBpsToSponsor: dp.maxBpsToSponsor,
127
- maxUserSlippageBps: dp.maxUserSlippageBps,
128
- finalRecipient: dp.finalRecipient,
129
- finalToken: dp.finalToken,
130
- destinationDex: dp.destinationDex,
131
- accountCreationMode: dp.accountCreationMode,
132
- executionMode: dp.executionMode,
133
- actionData: dp.actionData
165
+ burnToken: bytes32(uint256(uint160(inputToken))),
166
+ destinationCaller: routeParams.destinationCaller,
167
+ maxFee: (depositAmount * routeParams.cctpMaxFeeBps) / BPS_SCALAR,
168
+ minFinalityThreshold: routeParams.minFinalityThreshold,
169
+ nonce: submitterData.nonce,
170
+ deadline: submitterData.cctpDeadline,
171
+ maxBpsToSponsor: routeParams.maxBpsToSponsor,
172
+ maxUserSlippageBps: routeParams.maxUserSlippageBps,
173
+ finalRecipient: routeParams.finalRecipient,
174
+ finalToken: routeParams.finalToken,
175
+ destinationDex: routeParams.destinationDex,
176
+ accountCreationMode: routeParams.accountCreationMode,
177
+ executionMode: routeParams.executionMode,
178
+ actionData: routeParams.actionData
134
179
  }),
135
- sd.signature
180
+ submitterData.peripherySignature
136
181
  );
137
182
  }
138
183
  }
@@ -1,112 +1,90 @@
1
1
  // SPDX-License-Identifier: BUSL-1.1
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
5
- import { ICounterfactualDepositFactory } from "../../interfaces/ICounterfactualDepositFactory.sol";
4
+ import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
5
+ import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
6
+ import { CounterfactualDeposit } from "./CounterfactualDeposit.sol";
6
7
 
7
8
  /**
8
9
  * @title CounterfactualDepositFactory
9
- * @notice Generic factory for deploying counterfactual deposit addresses via CREATE2
10
- * @dev Bridge-agnostic: takes a pre-computed paramsHash and stores it in the clone's immutable args.
11
- * Each implementation defines its own immutables struct. The caller hashes the params off-chain.
10
+ * @notice Deterministically deploys counterfactual `BeaconProxy` instances. Each proxy uses the global
11
+ * `CounterfactualBeacon` as its beacon (so it always runs the registry's current implementation) and
12
+ * is initialized with its route root in the constructor `data`.
13
+ * @dev The `initialize(initialRoot)` call data is part of the proxy's init code, so for a fixed `salt`
14
+ * the address is `f(salt, initialRoot)`. Callers wanting one canonical address per destination
15
+ * identity (and automatic cross-chain parity) should pass `salt = 0`; a non-zero `salt` yields
16
+ * additional addresses for the same `initialRoot` and requires the caller to reuse the same `salt`
17
+ * on every chain for parity. The factory and the beacon must be deployed deterministically at
18
+ * identical addresses across chains for cross-chain address parity.
19
+ * `predictAddress` / `_initCode` / `_computeProxyAddress` are `virtual`/`internal` so chain-specific
20
+ * variants (e.g. Tron's 0x41 CREATE2 prefix) can override prediction.
12
21
  * @custom:security-contact bugs@across.to
13
22
  */
14
- contract CounterfactualDepositFactory is ICounterfactualDepositFactory {
15
- /**
16
- * @notice Deploys a counterfactual deposit contract
17
- * @param counterfactualDepositImplementation Implementation contract address
18
- * @param paramsHash keccak256 hash of the ABI-encoded route parameters
19
- * @param salt Unique salt for address generation
20
- * @return depositAddress Address of deployed contract
21
- */
22
- function deploy(
23
- address counterfactualDepositImplementation,
24
- bytes32 paramsHash,
25
- bytes32 salt
26
- ) public returns (address depositAddress) {
27
- depositAddress = Clones.cloneDeterministicWithImmutableArgs(
28
- counterfactualDepositImplementation,
29
- abi.encode(paramsHash),
30
- salt
31
- );
32
- emit DepositAddressCreated(depositAddress, counterfactualDepositImplementation, paramsHash, salt);
23
+ contract CounterfactualDepositFactory {
24
+ /// @notice The beacon (the `CounterfactualBeacon`) every deployed proxy points at.
25
+ address public immutable BEACON;
26
+
27
+ /// @notice Emitted when a counterfactual proxy is deployed.
28
+ event CounterfactualDeployed(address indexed counterfactual, bytes32 initialRoot);
29
+
30
+ constructor(address beacon) {
31
+ BEACON = beacon;
32
+ }
33
+
34
+ /// @notice Predict the proxy address for a given `salt` and `initialRoot`.
35
+ function predictAddress(bytes32 salt, bytes32 initialRoot) public view virtual returns (address) {
36
+ return _computeProxyAddress(salt, keccak256(_initCode(initialRoot)));
33
37
  }
34
38
 
35
- /**
36
- * @notice Forwards calldata to a deployed clone, bubbling up any revert
37
- * @param depositAddress Address of the deployed clone
38
- * @param executeCalldata Calldata to forward (e.g. abi.encodeCall of executeDeposit)
39
- */
40
- function execute(address depositAddress, bytes calldata executeCalldata) external payable {
41
- _execute(depositAddress, executeCalldata);
39
+ /// @notice Deploy the proxy for `salt` and `initialRoot` (already initialized + always-current via
40
+ /// the beacon). Reverts if already deployed.
41
+ function deploy(bytes32 salt, bytes32 initialRoot) public returns (address counterfactual) {
42
+ counterfactual = address(
43
+ new BeaconProxy{ salt: salt }(BEACON, abi.encodeCall(CounterfactualDeposit.initialize, (initialRoot)))
44
+ );
45
+ emit CounterfactualDeployed(counterfactual, initialRoot);
42
46
  }
43
47
 
44
- /**
45
- * @notice Deploys and executes a deposit in one transaction
46
- * @dev Reverts if the clone is already deployed. Use deployIfNeededAndExecute for idempotent behavior.
47
- * @param counterfactualDepositImplementation Implementation contract address
48
- * @param paramsHash keccak256 hash of the ABI-encoded route parameters
49
- * @param salt Unique salt for address generation
50
- * @param executeCalldata Calldata to forward to the clone (e.g. abi.encodeCall of executeDeposit)
51
- * @return depositAddress Address of deposit contract
52
- */
48
+ /// @notice Deploy, then forward `executeCalldata` to the proxy. Reverts if already deployed.
53
49
  function deployAndExecute(
54
- address counterfactualDepositImplementation,
55
- bytes32 paramsHash,
56
50
  bytes32 salt,
51
+ bytes32 initialRoot,
57
52
  bytes calldata executeCalldata
58
- ) external payable returns (address depositAddress) {
59
- depositAddress = deploy(counterfactualDepositImplementation, paramsHash, salt);
60
- _execute(depositAddress, executeCalldata);
53
+ ) external payable returns (address counterfactual) {
54
+ counterfactual = deploy(salt, initialRoot);
55
+ _execute(counterfactual, executeCalldata);
61
56
  }
62
57
 
63
- /**
64
- * @notice Deploys (if not already deployed) and executes a deposit in one transaction
65
- * @dev Unlike deployAndExecute, this does not revert if the clone already exists.
66
- * @param counterfactualDepositImplementation Implementation contract address
67
- * @param paramsHash keccak256 hash of the ABI-encoded route parameters
68
- * @param salt Unique salt for address generation
69
- * @param executeCalldata Calldata to forward to the clone (e.g. abi.encodeCall of executeDeposit)
70
- * @return depositAddress Address of deposit contract
71
- */
58
+ /// @notice Deploy if needed (idempotent), then forward `executeCalldata` to the proxy.
72
59
  function deployIfNeededAndExecute(
73
- address counterfactualDepositImplementation,
74
- bytes32 paramsHash,
75
60
  bytes32 salt,
61
+ bytes32 initialRoot,
76
62
  bytes calldata executeCalldata
77
- ) external payable returns (address depositAddress) {
78
- depositAddress = predictDepositAddress(counterfactualDepositImplementation, paramsHash, salt);
79
- if (depositAddress.code.length == 0) deploy(counterfactualDepositImplementation, paramsHash, salt);
80
- _execute(depositAddress, executeCalldata);
63
+ ) external payable returns (address counterfactual) {
64
+ counterfactual = predictAddress(salt, initialRoot);
65
+ if (counterfactual.code.length == 0) deploy(salt, initialRoot);
66
+ _execute(counterfactual, executeCalldata);
81
67
  }
82
68
 
83
- /**
84
- * @notice Predicts the address of a counterfactual deposit contract
85
- * @param counterfactualDepositImplementation Implementation contract address
86
- * @param paramsHash keccak256 hash of the ABI-encoded route parameters
87
- * @param salt Unique salt for address generation
88
- * @return Predicted address
89
- */
90
- function predictDepositAddress(
91
- address counterfactualDepositImplementation,
92
- bytes32 paramsHash,
93
- bytes32 salt
94
- ) public view virtual returns (address) {
69
+ /// @dev The proxy creation code (creation bytecode + constructor args) — `virtual` for Tron etc.
70
+ function _initCode(bytes32 initialRoot) internal view virtual returns (bytes memory) {
95
71
  return
96
- Clones.predictDeterministicAddressWithImmutableArgs(
97
- counterfactualDepositImplementation,
98
- abi.encode(paramsHash),
99
- salt
72
+ abi.encodePacked(
73
+ type(BeaconProxy).creationCode,
74
+ abi.encode(BEACON, abi.encodeCall(CounterfactualDeposit.initialize, (initialRoot)))
100
75
  );
101
76
  }
102
77
 
103
- /**
104
- * @dev Forwards calldata to a clone, bubbling up any revert.
105
- * @param depositAddress Address of the deployed clone.
106
- * @param executeCalldata Calldata to forward.
107
- */
108
- function _execute(address depositAddress, bytes calldata executeCalldata) private {
109
- (bool success, bytes memory returnData) = depositAddress.call{ value: msg.value }(executeCalldata);
78
+ /// @dev CREATE2 address derivation for a `salt` and the proxy init-code hash. `virtual` so
79
+ /// chain-specific variants (e.g. Tron's 0x41 prefix) can override the derivation. Deployment
80
+ /// via `deploy()` uses the `create2` opcode directly, which is correct on every chain; this
81
+ /// hook only governs off-chain-style prediction.
82
+ function _computeProxyAddress(bytes32 salt, bytes32 initCodeHash) internal view virtual returns (address) {
83
+ return Create2.computeAddress(salt, initCodeHash, address(this));
84
+ }
85
+
86
+ function _execute(address counterfactual, bytes calldata executeCalldata) private {
87
+ (bool success, bytes memory returnData) = counterfactual.call{ value: msg.value }(executeCalldata);
110
88
  if (!success) {
111
89
  assembly {
112
90
  revert(add(returnData, 32), mload(returnData))
@@ -6,31 +6,21 @@ import { CounterfactualDepositFactory } from "./CounterfactualDepositFactory.sol
6
6
 
7
7
  /**
8
8
  * @title CounterfactualDepositFactoryTron
9
- * @notice Tron-compatible factory for deploying counterfactual deposit addresses via CREATE2.
10
- * @dev Tron's TVM uses 0x41 instead of 0xff as the CREATE2 address derivation prefix.
11
- * OZ Clones deploys correctly (the create2 opcode natively uses 0x41 on Tron), but its
12
- * address prediction hardcodes 0xff. This factory overrides predictDepositAddress to use
13
- * TronClones with the correct 0x41 prefix. All other logic is inherited from the base factory.
9
+ * @notice Tron-compatible variant of `CounterfactualDepositFactory` for deploying counterfactual
10
+ * proxies via CREATE2 on the TVM.
11
+ * @dev Tron's TVM uses 0x41 instead of EVM's 0xff as the CREATE2 address derivation prefix. The
12
+ * `create2` opcode itself uses 0x41 natively, so deployment via the inherited `deploy(...)`
13
+ * (which uses `new BeaconProxy{ salt: 0 }(...)`) produces the correct address on Tron. Only
14
+ * OZ's `Create2.computeAddress` — used for off-chain-style prediction — hardcodes 0xff and would
15
+ * return the wrong address. This variant overrides the `_computeProxyAddress` hook to predict
16
+ * with the 0x41 prefix; all other logic is inherited unchanged.
17
+ * @custom:security-contact bugs@across.to
14
18
  */
15
19
  contract CounterfactualDepositFactoryTron is CounterfactualDepositFactory {
16
- /**
17
- * @notice Predicts the Tron CREATE2 address of a counterfactual deposit contract.
18
- * @dev Uses TronClones with the 0x41 prefix instead of OZ's 0xff to match TVM behavior.
19
- * @param counterfactualDepositImplementation Implementation contract address.
20
- * @param paramsHash keccak256 hash of the ABI-encoded route parameters.
21
- * @param salt Unique salt for address generation.
22
- * @return Predicted address of the clone on Tron.
23
- */
24
- function predictDepositAddress(
25
- address counterfactualDepositImplementation,
26
- bytes32 paramsHash,
27
- bytes32 salt
28
- ) public view override returns (address) {
29
- return
30
- TronClones.predictDeterministicAddressWithImmutableArgs(
31
- counterfactualDepositImplementation,
32
- abi.encode(paramsHash),
33
- salt
34
- );
20
+ constructor(address beacon) CounterfactualDepositFactory(beacon) {} // solhint-disable-line no-empty-blocks
21
+
22
+ /// @dev TRON OVERRIDE: predict the `BeaconProxy` CREATE2 address using the 0x41 prefix.
23
+ function _computeProxyAddress(bytes32 salt, bytes32 initCodeHash) internal view override returns (address) {
24
+ return TronClones.computeAddress(salt, initCodeHash, address(this));
35
25
  }
36
26
  }