@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.
- package/contracts/interfaces/ICounterfactualBeacon.sol +91 -0
- package/contracts/interfaces/ICounterfactualDeposit.sol +21 -0
- package/contracts/libraries/TronClones.sol +13 -0
- package/contracts/periphery/counterfactual/CounterfactualBeacon.sol +115 -0
- package/contracts/periphery/counterfactual/CounterfactualBeaconBase.sol +121 -0
- package/contracts/periphery/counterfactual/CounterfactualBeaconBootstrap.sol +39 -0
- package/contracts/periphery/counterfactual/CounterfactualDeposit.sol +109 -28
- package/contracts/periphery/counterfactual/CounterfactualDepositCCTP.sol +102 -57
- package/contracts/periphery/counterfactual/CounterfactualDepositFactory.sol +61 -83
- package/contracts/periphery/counterfactual/CounterfactualDepositFactoryTron.sol +14 -24
- package/contracts/periphery/counterfactual/CounterfactualDepositOFT.sol +119 -58
- package/contracts/periphery/counterfactual/CounterfactualDepositSpokePool.sol +122 -90
- package/contracts/periphery/counterfactual/CounterfactualDepositSpokePoolTr.sol +10 -21
- package/contracts/periphery/counterfactual/CounterfactualDepositVanillaCCTP.sol +170 -0
- package/contracts/periphery/counterfactual/CounterfactualImplementationBase.sol +51 -0
- package/dist/broadcast/deployed-addresses.json +6 -6
- package/dist/evm/artifacts/AdminWithdrawManager.sol/AdminWithdrawManager.json +1 -1
- package/dist/evm/artifacts/CounterfactualBeacon.sol/CounterfactualBeacon.json +1 -0
- package/dist/evm/artifacts/CounterfactualBeaconBase.sol/CounterfactualBeaconBase.json +1 -0
- package/dist/evm/artifacts/CounterfactualBeaconBase.sol/IBeaconTarget.json +1 -0
- package/dist/evm/artifacts/CounterfactualBeaconBootstrap.sol/CounterfactualBeaconBootstrap.json +1 -0
- package/dist/evm/artifacts/CounterfactualDeposit.sol/CounterfactualDeposit.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositCCTP.sol/CounterfactualDepositCCTP.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositFactory.sol/CounterfactualDepositFactory.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositFactoryTron.sol/CounterfactualDepositFactoryTron.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositOFT.sol/CounterfactualDepositOFT.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositOFT.sol/ISponsoredOFTSrcPeriphery.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositSpokePool.sol/CounterfactualDepositSpokePool.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositSpokePoolTr.sol/CounterfactualDepositSpokePoolTr.json +1 -1
- package/dist/evm/artifacts/CounterfactualDepositVanillaCCTP.sol/CounterfactualDepositVanillaCCTP.json +1 -0
- package/dist/evm/artifacts/CounterfactualImplementationBase.sol/CounterfactualImplementationBase.json +1 -0
- package/dist/evm/artifacts/CounterfactualTestBase.sol/CounterfactualTestBase.json +1 -0
- package/dist/evm/artifacts/ICounterfactualBeacon.sol/ICounterfactualBeacon.json +1 -0
- package/dist/evm/artifacts/ICounterfactualDeposit.sol/ICounterfactualDeposit.json +1 -1
- package/dist/evm/artifacts/Ownable2StepUpgradeable.sol/Ownable2StepUpgradeable.json +1 -0
- package/dist/evm/artifacts/OwnableUpgradeable.sol/OwnableUpgradeable.json +1 -1
- package/dist/evm/artifacts/UUPSUpgradeable.sol/UUPSUpgradeable.json +1 -1
- package/package.json +1 -1
- package/dist/evm/artifacts/Clones.sol/Clones.json +0 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title ICounterfactualBeacon
|
|
8
|
+
* @notice Global, per-chain registry and **beacon** for every counterfactual `BeaconProxy`:
|
|
9
|
+
* `implementation()` is the single implementation all proxies run; `upgradeRoot()` is the
|
|
10
|
+
* `(proxy, latestRoot)` tree authorizing best-effort per-proxy root updates. It is also the single
|
|
11
|
+
* source of every chain-specific value the leaves need (bridge endpoints, domains/EIDs, fee
|
|
12
|
+
* `signer`, token addresses), exposed as `public immutable` getters so leaves stay byte-identical
|
|
13
|
+
* across chains. Changing any value (or adding a token) is a UUPS upgrade; the proxy address never
|
|
14
|
+
* changes.
|
|
15
|
+
* @dev `implementation()` is the **counterfactual** implementation (beacon target), not the registry's own.
|
|
16
|
+
* @custom:security-contact bugs@across.to
|
|
17
|
+
*/
|
|
18
|
+
interface ICounterfactualBeacon is IBeacon {
|
|
19
|
+
/// @notice Emitted when the admin sets the global implementation (the beacon target).
|
|
20
|
+
event ImplementationSet(address indexed implementation);
|
|
21
|
+
|
|
22
|
+
/// @notice Emitted when the admin sets the upgrade-tree root.
|
|
23
|
+
event UpgradeRootSet(bytes32 indexed upgradeRoot);
|
|
24
|
+
|
|
25
|
+
// `implementation()` is inherited from `IBeacon` — the canonical implementation every counterfactual
|
|
26
|
+
// proxy runs (resolved live by each `BeaconProxy`).
|
|
27
|
+
|
|
28
|
+
/// @notice Root of the `(proxy, latestRoot)` merkle tree authorizing per-proxy root updates.
|
|
29
|
+
function upgradeRoot() external view returns (bytes32);
|
|
30
|
+
|
|
31
|
+
// --- Chain-specific config (immutable; read by leaf implementations under delegatecall) ---
|
|
32
|
+
|
|
33
|
+
/// @notice Off-chain signer that authorizes runtime execution fees for every leaf implementation.
|
|
34
|
+
function signer() external view returns (address);
|
|
35
|
+
|
|
36
|
+
/// @notice Across SpokePool on this chain.
|
|
37
|
+
function spokePool() external view returns (address);
|
|
38
|
+
|
|
39
|
+
/// @notice Wrapped native token (e.g. WETH) used as the SpokePool input token for native deposits.
|
|
40
|
+
function wrappedNativeToken() external view returns (address);
|
|
41
|
+
|
|
42
|
+
/// @notice Input token for the "native" SpokePool route: the native sentinel
|
|
43
|
+
/// (`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`) where the deposit is `msg.value` (wrapped to
|
|
44
|
+
/// `wrappedNativeToken()`), or an ERC-20 on chains with no native gas token. The SpokePool leaf
|
|
45
|
+
/// names this via `inputTokenGetter` and branches on the sentinel, so one leaf serves both.
|
|
46
|
+
function nativeToken() external view returns (address);
|
|
47
|
+
|
|
48
|
+
/// @notice SponsoredCCTPSrcPeriphery on this chain (sponsored CCTP route).
|
|
49
|
+
function cctpSrcPeriphery() external view returns (address);
|
|
50
|
+
|
|
51
|
+
/// @notice Circle CCTP v2 TokenMessenger on this chain (vanilla CCTP route).
|
|
52
|
+
function cctpTokenMessenger() external view returns (address);
|
|
53
|
+
|
|
54
|
+
/// @notice Circle CCTP source domain id for this chain (sponsored CCTP route).
|
|
55
|
+
function cctpSourceDomain() external view returns (uint32);
|
|
56
|
+
|
|
57
|
+
/// @notice SponsoredOFTSrcPeriphery (USDT0 today). OFT peripheries are single-token; the OFT leaf picks
|
|
58
|
+
/// which to use by this getter's selector, so another OFT token = another getter (beacon upgrade).
|
|
59
|
+
function oftSrcPeriphery() external view returns (address);
|
|
60
|
+
|
|
61
|
+
/// @notice LayerZero OFT source endpoint id for this chain.
|
|
62
|
+
function oftSrcEid() external view returns (uint32);
|
|
63
|
+
|
|
64
|
+
/// @notice USDC token address on this chain.
|
|
65
|
+
function usdc() external view returns (address);
|
|
66
|
+
|
|
67
|
+
/// @notice USDT token address on this chain.
|
|
68
|
+
function usdt() external view returns (address);
|
|
69
|
+
|
|
70
|
+
// --- Per-(token, bridge) execution-fee caps (input-token units). A leaf names which to enforce via its
|
|
71
|
+
// `maxExecutionFeeGetter` selector. Illustrative set; for SpokePool this is the fixed fee component. ---
|
|
72
|
+
|
|
73
|
+
/// @notice Max execution fee for the USDC CCTP route(s).
|
|
74
|
+
function usdcCctpMaxExecutionFee() external view returns (uint256);
|
|
75
|
+
|
|
76
|
+
/// @notice Cap on the submitter-chosen Circle fast-transfer fee (vanilla CCTP route), in bps of the
|
|
77
|
+
/// burned amount; 0 ⇒ standard transfers only.
|
|
78
|
+
function usdcCctpMaxFeeBps() external view returns (uint256);
|
|
79
|
+
|
|
80
|
+
/// @notice Max execution fee for the USDT OFT route.
|
|
81
|
+
function usdtOftMaxExecutionFee() external view returns (uint256);
|
|
82
|
+
|
|
83
|
+
/// @notice Max (fixed) fee for the USDC SpokePool route.
|
|
84
|
+
function usdcSpokePoolMaxExecutionFee() external view returns (uint256);
|
|
85
|
+
|
|
86
|
+
/// @notice Max (fixed) fee for the USDT SpokePool route.
|
|
87
|
+
function usdtSpokePoolMaxExecutionFee() external view returns (uint256);
|
|
88
|
+
|
|
89
|
+
/// @notice Max (fixed) fee for the WETH/native SpokePool route.
|
|
90
|
+
function wethSpokePoolMaxExecutionFee() external view returns (uint256);
|
|
91
|
+
}
|
|
@@ -23,4 +23,25 @@ interface ICounterfactualDeposit {
|
|
|
23
23
|
bytes calldata submitterData,
|
|
24
24
|
bytes32[] calldata proof
|
|
25
25
|
) external payable;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @notice Update `activeRoot` to `newRoot` (if not already there), then execute, atomically.
|
|
29
|
+
* @dev Lets an executor activate a newly-added route and use it in one transaction. The root
|
|
30
|
+
* update is skipped when the proxy is already at `newRoot`, so this never reverts
|
|
31
|
+
* `RootUnchanged` for an already-current proxy.
|
|
32
|
+
* @param newRoot The root to bring the proxy to before executing.
|
|
33
|
+
* @param updateProof Merkle proof for the (proxy, newRoot) leaf in the beacon's upgrade tree.
|
|
34
|
+
* @param implementation The implementation contract to delegatecall.
|
|
35
|
+
* @param params ABI-encoded route parameters (hashed into the merkle leaf).
|
|
36
|
+
* @param submitterData ABI-encoded data supplied by the caller at execution time.
|
|
37
|
+
* @param executeProof Merkle proof for the (implementation, keccak256(params)) leaf.
|
|
38
|
+
*/
|
|
39
|
+
function updateRootAndExecute(
|
|
40
|
+
bytes32 newRoot,
|
|
41
|
+
bytes32[] calldata updateProof,
|
|
42
|
+
address implementation,
|
|
43
|
+
bytes calldata params,
|
|
44
|
+
bytes calldata submitterData,
|
|
45
|
+
bytes32[] calldata executeProof
|
|
46
|
+
) external payable;
|
|
26
47
|
}
|
|
@@ -50,6 +50,19 @@ library TronClones {
|
|
|
50
50
|
return _computeTronAddress(salt, bytecodeHash, deployer);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @notice Generic Tron CREATE2 address prediction (0x41 prefix) for an arbitrary init-code hash.
|
|
55
|
+
* @dev Mirrors OZ `Create2.computeAddress` but uses Tron's 0x41 prefix. Use for non-clone init code
|
|
56
|
+
* (e.g. an `ERC1967Proxy`) where the caller already has `keccak256(initCode)`.
|
|
57
|
+
* @param salt The CREATE2 salt.
|
|
58
|
+
* @param bytecodeHash The keccak256 hash of the init code.
|
|
59
|
+
* @param deployer The deploying contract address.
|
|
60
|
+
* @return The predicted address on Tron.
|
|
61
|
+
*/
|
|
62
|
+
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) {
|
|
63
|
+
return _computeTronAddress(salt, bytecodeHash, deployer);
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
/**
|
|
54
67
|
* @dev Tron CREATE2 address computation using 0x41 prefix.
|
|
55
68
|
* Memory layout mirrors OZ Create2.computeAddress but replaces 0xff with 0x41.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import { ICounterfactualBeacon } from "../../interfaces/ICounterfactualBeacon.sol";
|
|
5
|
+
import { CounterfactualBeaconBase } from "./CounterfactualBeaconBase.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @notice Chain-specific config baked into a `CounterfactualBeacon` implementation as `public immutable`s,
|
|
9
|
+
* so leaves read endpoints/tokens/signer from the registry and stay byte-identical across chains.
|
|
10
|
+
* Each chain deploys its own implementation with its own config.
|
|
11
|
+
*/
|
|
12
|
+
struct CounterfactualChainConfig {
|
|
13
|
+
address signer;
|
|
14
|
+
address spokePool;
|
|
15
|
+
address wrappedNativeToken;
|
|
16
|
+
/// @dev "Native" SpokePool route: the native sentinel where the deposit is `msg.value` (wrapped to
|
|
17
|
+
/// `wrappedNativeToken`), or an ERC-20 on chains with no native gas token. See `nativeToken()`.
|
|
18
|
+
address nativeToken;
|
|
19
|
+
address cctpSrcPeriphery;
|
|
20
|
+
address cctpTokenMessenger;
|
|
21
|
+
uint32 cctpSourceDomain;
|
|
22
|
+
/// @dev Single-token OFT periphery (USDT0). The OFT leaf picks the periphery by its getter selector, so
|
|
23
|
+
/// another OFT token is a beacon upgrade adding another getter.
|
|
24
|
+
address oftSrcPeriphery;
|
|
25
|
+
uint32 oftSrcEid;
|
|
26
|
+
address usdc;
|
|
27
|
+
address usdt;
|
|
28
|
+
/// @dev Per-(token, bridge) execution-fee caps, in input-token units. A leaf names which to enforce via
|
|
29
|
+
/// a `bytes4` selector (its `maxExecutionFeeGetter`). Illustrative set — add more as routes need them.
|
|
30
|
+
/// For SpokePool this is the fixed component of the fee cap (added to the leaf's `maxFeeBps` term).
|
|
31
|
+
uint256 usdcCctpMaxExecutionFee;
|
|
32
|
+
/// @dev Cap on the submitter-chosen Circle fast-transfer fee (vanilla CCTP route), in bps of the
|
|
33
|
+
/// burned amount (0 ⇒ standard transfers only).
|
|
34
|
+
uint256 usdcCctpMaxFeeBps;
|
|
35
|
+
uint256 usdtOftMaxExecutionFee;
|
|
36
|
+
uint256 usdcSpokePoolMaxExecutionFee;
|
|
37
|
+
uint256 usdtSpokePoolMaxExecutionFee;
|
|
38
|
+
uint256 wethSpokePoolMaxExecutionFee;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @title CounterfactualBeacon
|
|
43
|
+
* @notice The **configuration** of the per-chain counterfactual registry/beacon: every chain-specific value
|
|
44
|
+
* (bridge endpoints, domains/EIDs, fee signer, token addresses, fee caps) as a `public immutable`,
|
|
45
|
+
* named getter. All logic — root/implementation management, UUPS, ownership — lives in
|
|
46
|
+
* `CounterfactualBeaconBase`.
|
|
47
|
+
* @dev The config is `public immutable` (in code, readable through the proxy under delegatecall), so changing
|
|
48
|
+
* a value or adding a token/cap means deploying a new implementation and `upgradeToAndCall`-ing to it.
|
|
49
|
+
* For an identical proxy address across chains, deploy against a uniform bootstrap then upgrade to the
|
|
50
|
+
* chain-specific implementation.
|
|
51
|
+
*
|
|
52
|
+
* NOTE: these `immutable` values are **pure configuration**. A new implementation that changes only
|
|
53
|
+
* them (this contract's constructor wiring, with no change to `CounterfactualBeaconBase`) is a
|
|
54
|
+
* configuration change and is **not subject to audit** — only changes to the base's *logic* are audited.
|
|
55
|
+
* @custom:security-contact bugs@across.to
|
|
56
|
+
*/
|
|
57
|
+
contract CounterfactualBeacon is CounterfactualBeaconBase {
|
|
58
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
59
|
+
address public immutable signer;
|
|
60
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
61
|
+
address public immutable spokePool;
|
|
62
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
63
|
+
address public immutable wrappedNativeToken;
|
|
64
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
65
|
+
address public immutable nativeToken;
|
|
66
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
67
|
+
address public immutable cctpSrcPeriphery;
|
|
68
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
69
|
+
address public immutable cctpTokenMessenger;
|
|
70
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
71
|
+
uint32 public immutable cctpSourceDomain;
|
|
72
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
73
|
+
address public immutable oftSrcPeriphery;
|
|
74
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
75
|
+
uint32 public immutable oftSrcEid;
|
|
76
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
77
|
+
address public immutable usdc;
|
|
78
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
79
|
+
address public immutable usdt;
|
|
80
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
81
|
+
uint256 public immutable usdcCctpMaxExecutionFee;
|
|
82
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
83
|
+
uint256 public immutable usdcCctpMaxFeeBps;
|
|
84
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
85
|
+
uint256 public immutable usdtOftMaxExecutionFee;
|
|
86
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
87
|
+
uint256 public immutable usdcSpokePoolMaxExecutionFee;
|
|
88
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
89
|
+
uint256 public immutable usdtSpokePoolMaxExecutionFee;
|
|
90
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
91
|
+
uint256 public immutable wethSpokePoolMaxExecutionFee;
|
|
92
|
+
|
|
93
|
+
/// @param config The chain-specific configuration baked into this implementation (see
|
|
94
|
+
/// `CounterfactualChainConfig`). Each field becomes an immutable, named getter.
|
|
95
|
+
constructor(CounterfactualChainConfig memory config) {
|
|
96
|
+
signer = config.signer;
|
|
97
|
+
spokePool = config.spokePool;
|
|
98
|
+
wrappedNativeToken = config.wrappedNativeToken;
|
|
99
|
+
nativeToken = config.nativeToken;
|
|
100
|
+
cctpSrcPeriphery = config.cctpSrcPeriphery;
|
|
101
|
+
cctpTokenMessenger = config.cctpTokenMessenger;
|
|
102
|
+
cctpSourceDomain = config.cctpSourceDomain;
|
|
103
|
+
oftSrcPeriphery = config.oftSrcPeriphery;
|
|
104
|
+
oftSrcEid = config.oftSrcEid;
|
|
105
|
+
usdc = config.usdc;
|
|
106
|
+
usdt = config.usdt;
|
|
107
|
+
usdcCctpMaxExecutionFee = config.usdcCctpMaxExecutionFee;
|
|
108
|
+
usdcCctpMaxFeeBps = config.usdcCctpMaxFeeBps;
|
|
109
|
+
usdtOftMaxExecutionFee = config.usdtOftMaxExecutionFee;
|
|
110
|
+
usdcSpokePoolMaxExecutionFee = config.usdcSpokePoolMaxExecutionFee;
|
|
111
|
+
usdtSpokePoolMaxExecutionFee = config.usdtSpokePoolMaxExecutionFee;
|
|
112
|
+
wethSpokePoolMaxExecutionFee = config.wethSpokePoolMaxExecutionFee;
|
|
113
|
+
_disableInitializers();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
5
|
+
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
|
+
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
|
|
7
|
+
import { IBeacon } from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
|
|
8
|
+
import { ICounterfactualBeacon } from "../../interfaces/ICounterfactualBeacon.sol";
|
|
9
|
+
|
|
10
|
+
/// @dev Minimal view used to verify a candidate beacon target is bound to this beacon — every
|
|
11
|
+
/// counterfactual implementation embeds its beacon as the immutable `BEACON` (for `updateRoot`).
|
|
12
|
+
interface IBeaconTarget {
|
|
13
|
+
function BEACON() external view returns (address);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @title CounterfactualBeaconBase
|
|
18
|
+
* @notice The **logic** of the counterfactual registry/beacon: the mutable `implementation` (beacon target)
|
|
19
|
+
* every proxy runs, the `upgradeRoot` authorizing per-proxy root updates, UUPS upgradeability and the
|
|
20
|
+
* `Ownable2Step` admin. The chain-specific configuration (endpoints, tokens, fee signer, fee caps)
|
|
21
|
+
* lives in the derived `CounterfactualBeacon` as immutables. Splitting it out keeps the audit
|
|
22
|
+
* boundary clean: this base is the reviewable logic; a config-only change is a new derived contract
|
|
23
|
+
* that touches no logic here (see `CounterfactualBeacon`).
|
|
24
|
+
* @dev Abstract — the config getters declared in `ICounterfactualBeacon` are implemented by the derived
|
|
25
|
+
* contract's immutables. A UUPS proxy, so the registry address is permanent (anchoring every
|
|
26
|
+
* `BeaconProxy`) while logic/config evolve. `Ownable2Step` admin (no timelock) — it can retarget every
|
|
27
|
+
* proxy instantly, so use a trusted multisig. `implementation()` is the beacon target, not the
|
|
28
|
+
* registry's own UUPS implementation.
|
|
29
|
+
* @custom:security-contact bugs@across.to
|
|
30
|
+
*/
|
|
31
|
+
abstract contract CounterfactualBeaconBase is
|
|
32
|
+
ICounterfactualBeacon,
|
|
33
|
+
Initializable,
|
|
34
|
+
UUPSUpgradeable,
|
|
35
|
+
Ownable2StepUpgradeable
|
|
36
|
+
{
|
|
37
|
+
/// @custom:storage-location erc7201:across.counterfactual.beacon.storage
|
|
38
|
+
struct RegistryStorage {
|
|
39
|
+
address implementation;
|
|
40
|
+
bytes32 upgradeRoot;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// @dev Implementation target is not a contract.
|
|
44
|
+
error NotAContract();
|
|
45
|
+
/// @dev Implementation target's `BEACON()` does not point back at this beacon.
|
|
46
|
+
error WrongBeacon();
|
|
47
|
+
|
|
48
|
+
// keccak256(abi.encode(uint256(keccak256("across.counterfactual.beacon.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
49
|
+
bytes32 private constant STORAGE_LOCATION = 0xb8f0bb8c74633417634f6191ee000dac3f927914fa2e1d714b73a72668a01500;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @notice Initialize the registry's mutable storage (chain config comes from the derived constructor).
|
|
53
|
+
* @dev With the bootstrap→upgrade deploy the proxy is initialized by the bootstrap and this is never
|
|
54
|
+
* reached (initializer already consumed); set `implementation`/`upgradeRoot` via the owner setters.
|
|
55
|
+
* @param owner_ The admin (use a multisig).
|
|
56
|
+
* @param implementation_ Initial beacon target (may be address(0), set later via `setImplementation`).
|
|
57
|
+
* @param upgradeRoot_ Initial upgrade-tree root (may be 0, set later).
|
|
58
|
+
*/
|
|
59
|
+
function initialize(address owner_, address implementation_, bytes32 upgradeRoot_) external initializer {
|
|
60
|
+
__Ownable_init(owner_);
|
|
61
|
+
__Ownable2Step_init();
|
|
62
|
+
// Allow `address(0)` for lazy init (the standard deploy flow is beacon → impl → setImplementation);
|
|
63
|
+
// otherwise the target must be a contract bound to this beacon, matching `setImplementation`.
|
|
64
|
+
if (implementation_ != address(0)) _validateImplementation(implementation_);
|
|
65
|
+
_setImplementation(implementation_);
|
|
66
|
+
_setUpgradeRoot(upgradeRoot_);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// @inheritdoc IBeacon
|
|
70
|
+
/// @dev The counterfactual implementation every `BeaconProxy` resolves and delegatecalls.
|
|
71
|
+
function implementation() external view returns (address) {
|
|
72
|
+
return _getStorage().implementation;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @inheritdoc ICounterfactualBeacon
|
|
76
|
+
function upgradeRoot() external view returns (bytes32) {
|
|
77
|
+
return _getStorage().upgradeRoot;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// @notice Set the global implementation (the beacon target) every proxy runs. Must be a contract
|
|
81
|
+
/// bound to this beacon; setting it instantly retargets all counterfactual proxies.
|
|
82
|
+
function setImplementation(address newImplementation) external onlyOwner {
|
|
83
|
+
_validateImplementation(newImplementation);
|
|
84
|
+
_setImplementation(newImplementation);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// @notice Set the root of the `(proxy, latestRoot)` upgrade tree.
|
|
88
|
+
function setUpgradeRoot(bytes32 newUpgradeRoot) external onlyOwner {
|
|
89
|
+
_setUpgradeRoot(newUpgradeRoot);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// @dev A valid target is a contract whose immutable `BEACON()` points back here — guarding against
|
|
93
|
+
/// retargeting every proxy to logic bound to a different beacon (which would brick `updateRoot`).
|
|
94
|
+
/// The `try` tolerates non-conforming targets: they leave `boundBeacon == 0` and revert below.
|
|
95
|
+
function _validateImplementation(address impl) private view {
|
|
96
|
+
if (impl.code.length == 0) revert NotAContract();
|
|
97
|
+
address boundBeacon;
|
|
98
|
+
try IBeaconTarget(impl).BEACON() returns (address b) {
|
|
99
|
+
boundBeacon = b;
|
|
100
|
+
} catch {}
|
|
101
|
+
if (boundBeacon != address(this)) revert WrongBeacon();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function _setImplementation(address newImplementation) internal {
|
|
105
|
+
_getStorage().implementation = newImplementation;
|
|
106
|
+
emit ImplementationSet(newImplementation);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _setUpgradeRoot(bytes32 newUpgradeRoot) internal {
|
|
110
|
+
_getStorage().upgradeRoot = newUpgradeRoot;
|
|
111
|
+
emit UpgradeRootSet(newUpgradeRoot);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _authorizeUpgrade(address) internal override onlyOwner {}
|
|
115
|
+
|
|
116
|
+
function _getStorage() private pure returns (RegistryStorage storage $) {
|
|
117
|
+
assembly {
|
|
118
|
+
$.slot := STORAGE_LOCATION
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
5
|
+
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
|
+
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title CounterfactualBeaconBootstrap
|
|
10
|
+
* @notice Minimal, chain-identical UUPS implementation used only to deploy the `CounterfactualBeacon`
|
|
11
|
+
* proxy at the **same address on every chain**.
|
|
12
|
+
* @dev The real `CounterfactualBeacon` implementation bakes chain-specific immutables in, so its address —
|
|
13
|
+
* and therefore the proxy's (the implementation is in the proxy init code) — differs per chain. This
|
|
14
|
+
* bootstrap has no immutables, so it has one deterministic address everywhere; the proxy is created
|
|
15
|
+
* against it then `upgradeToAndCall`-ed to the chain-specific `CounterfactualBeacon`. After that, set
|
|
16
|
+
* `implementation`/`upgradeRoot` via the owner setters (the initializer slot is already consumed, so
|
|
17
|
+
* `CounterfactualBeacon.initialize` can't rerun). Shares the OZ `Ownable2Step`/`UUPS` storage layout,
|
|
18
|
+
* so the admin set here is preserved.
|
|
19
|
+
*
|
|
20
|
+
* **Bytecode pinning:** the chain-invariant proxy address depends on this contract's exact creation
|
|
21
|
+
* code, so any bytecode change (solc/optimizer/imports/AST) moves the Bootstrap and every dependent
|
|
22
|
+
* address. Once the canonical Bootstrap ships on the first chain, deploy later chains from the saved
|
|
23
|
+
* canonical creation code, not a recompile — treat it as frozen and flag any PR touching it, its OZ
|
|
24
|
+
* imports, or its compiler profile.
|
|
25
|
+
* @custom:security-contact bugs@across.to
|
|
26
|
+
*/
|
|
27
|
+
contract CounterfactualBeaconBootstrap is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable {
|
|
28
|
+
constructor() {
|
|
29
|
+
_disableInitializers();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// @notice Set the admin that will perform the upgrade to the chain-specific implementation.
|
|
33
|
+
function initialize(address owner_) external initializer {
|
|
34
|
+
__Ownable_init(owner_);
|
|
35
|
+
__Ownable2Step_init();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _authorizeUpgrade(address) internal override onlyOwner {}
|
|
39
|
+
}
|
|
@@ -1,53 +1,119 @@
|
|
|
1
1
|
// SPDX-License-Identifier: BUSL-1.1
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
5
5
|
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
|
6
|
-
import {
|
|
6
|
+
import { ICounterfactualBeacon } from "../../interfaces/ICounterfactualBeacon.sol";
|
|
7
7
|
import { ICounterfactualDeposit } from "../../interfaces/ICounterfactualDeposit.sol";
|
|
8
|
+
import { ICounterfactualImplementation } from "../../interfaces/ICounterfactualImplementation.sol";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @title CounterfactualDeposit
|
|
11
|
-
* @notice
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* @notice The counterfactual implementation — the merkle-dispatched entry point every counterfactual
|
|
13
|
+
* `BeaconProxy` runs (the registry/beacon's `implementation()`). It also owns the proxy's
|
|
14
|
+
* mutable state: `activeRoot` (the route tree), held in an ERC-7201 namespaced slot. Verifies a
|
|
15
|
+
* leaf against `activeRoot`, then delegatecalls the per-bridge implementation the leaf authorizes
|
|
16
|
+
* (which decodes the destination identity from `params` and bridges).
|
|
17
|
+
* @dev Resolved live from the beacon (`CounterfactualBeacon.implementation()`) on every call, so all proxies
|
|
18
|
+
* always run the current implementation — there is no per-proxy upgrade and no bootstrap. Runs
|
|
19
|
+
* under the proxy's delegatecall, so `address(this)` is the proxy (correct for EIP-712 domains and
|
|
20
|
+
* token balances), `msg.sender` is the original caller, `msg.value` the original value.
|
|
14
21
|
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
22
|
+
* The implementation is upgraded **globally** by the admin setting the beacon's `implementation`;
|
|
23
|
+
* only the per-proxy `activeRoot` is mutable here, via the permissionless `updateRoot` (proven
|
|
24
|
+
* against the registry's `(proxy, latestRoot)` tree). Root updates are **best-effort** — a proxy
|
|
25
|
+
* keeps its `activeRoot` until someone updates it; there is no on-chain version/min-version gate.
|
|
26
|
+
* **Every future implementation version MUST preserve this ERC-7201 storage layout.**
|
|
19
27
|
*
|
|
20
|
-
* Note:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* Note: every leaf implementation's fee signature binds the leaf's route via `routeParamsHash` (the
|
|
29
|
+
* EIP-712 typehash for all four — SpokePool, CCTP, VanillaCCTP, OFT — commits it). So a fee signature
|
|
30
|
+
* authored for one leaf cannot be replayed against another, and a clone's tree MAY safely contain
|
|
31
|
+
* multiple leaves that share an implementation address (e.g. two OFT routes for different input tokens
|
|
32
|
+
* to one destination identity). Cross-chain replay is independently prevented by the `chainId` in the
|
|
33
|
+
* EIP-712 domain, and cross-clone replay by `verifyingContract = address(this)`.
|
|
34
|
+
* @custom:security-contact bugs@across.to
|
|
27
35
|
*/
|
|
28
|
-
contract CounterfactualDeposit is ICounterfactualDeposit {
|
|
29
|
-
/// @
|
|
36
|
+
contract CounterfactualDeposit is Initializable, ICounterfactualDeposit {
|
|
37
|
+
/// @custom:storage-location erc7201:across.counterfactual.upgradeable.storage
|
|
38
|
+
struct CounterfactualStorage {
|
|
39
|
+
bytes32 activeRoot;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// keccak256(abi.encode(uint256(keccak256("across.counterfactual.upgradeable.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
43
|
+
bytes32 private constant STORAGE_LOCATION = 0x5b89d334b964a560e5498fb6b9c95b4213116f116bbd1e59c9c85ba952217700;
|
|
44
|
+
|
|
45
|
+
/// @notice The `CounterfactualBeacon` — the beacon every counterfactual `BeaconProxy` resolves its
|
|
46
|
+
/// implementation from, and the source of the `upgradeRoot` used by `updateRoot`.
|
|
47
|
+
ICounterfactualBeacon public immutable BEACON;
|
|
48
|
+
|
|
49
|
+
/// @notice Emitted when `activeRoot` is updated via the upgrade tree.
|
|
50
|
+
event RootUpdated(bytes32 newRoot);
|
|
51
|
+
|
|
52
|
+
/// @dev Merkle proof against the registry's `(proxy, latestRoot)` tree failed.
|
|
53
|
+
error InvalidUpgradeProof();
|
|
54
|
+
/// @dev New root equals the current `activeRoot` (no-op).
|
|
55
|
+
error RootUnchanged();
|
|
56
|
+
|
|
57
|
+
constructor(ICounterfactualBeacon beacon) {
|
|
58
|
+
BEACON = beacon;
|
|
59
|
+
_disableInitializers();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// @notice Initialize the proxy's `activeRoot` from `initialRoot`.
|
|
63
|
+
/// @dev Delegatecalled once by the `BeaconProxy` constructor (its `data` carries `initialRoot`, which
|
|
64
|
+
/// thereby enters the CREATE2 preimage — binding the address to `initialRoot`).
|
|
65
|
+
function initialize(bytes32 initialRoot) external initializer {
|
|
66
|
+
_getStorage().activeRoot = initialRoot;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// @dev Accept native value sent to the proxy (deposits before/after deployment, refunds).
|
|
30
70
|
receive() external payable {}
|
|
31
71
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*/
|
|
72
|
+
/// @notice The merkle root authorizing this proxy's deposit routes.
|
|
73
|
+
function activeRoot() public view returns (bytes32) {
|
|
74
|
+
return _getStorage().activeRoot;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// @inheritdoc ICounterfactualDeposit
|
|
39
78
|
function execute(
|
|
40
79
|
address implementation,
|
|
41
80
|
bytes calldata params,
|
|
42
81
|
bytes calldata submitterData,
|
|
43
82
|
bytes32[] calldata proof
|
|
44
83
|
) external payable {
|
|
45
|
-
|
|
84
|
+
_execute(implementation, params, submitterData, proof);
|
|
85
|
+
}
|
|
46
86
|
|
|
87
|
+
/// @inheritdoc ICounterfactualDeposit
|
|
88
|
+
function updateRootAndExecute(
|
|
89
|
+
bytes32 newRoot,
|
|
90
|
+
bytes32[] calldata updateProof,
|
|
91
|
+
address implementation,
|
|
92
|
+
bytes calldata params,
|
|
93
|
+
bytes calldata submitterData,
|
|
94
|
+
bytes32[] calldata executeProof
|
|
95
|
+
) external payable {
|
|
96
|
+
// Skip the update (and its `RootUnchanged` revert) when the proxy is already current. NOTE:
|
|
97
|
+
// `updateProof` is therefore NOT validated in that case — there is no root change to authorize.
|
|
98
|
+
if (newRoot != activeRoot()) _updateRoot(newRoot, updateProof);
|
|
99
|
+
_execute(implementation, params, submitterData, executeProof);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// @notice Update `activeRoot`, proving `(address(this), newRoot)` is in the registry's upgrade tree.
|
|
103
|
+
/// @dev Permissionless. Root updates are best-effort — a proxy keeps its `activeRoot` until updated.
|
|
104
|
+
function updateRoot(bytes32 newRoot, bytes32[] calldata proof) external {
|
|
105
|
+
_updateRoot(newRoot, proof);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function _execute(
|
|
109
|
+
address implementation,
|
|
110
|
+
bytes calldata params,
|
|
111
|
+
bytes calldata submitterData,
|
|
112
|
+
bytes32[] calldata proof
|
|
113
|
+
) private {
|
|
47
114
|
// Double-hash to prevent leaf/internal-node ambiguity (OpenZeppelin standard).
|
|
48
115
|
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(implementation, keccak256(params)))));
|
|
49
|
-
|
|
50
|
-
if (!MerkleProof.verify(proof, merkleRoot, leaf)) revert InvalidProof();
|
|
116
|
+
if (!MerkleProof.verify(proof, activeRoot(), leaf)) revert InvalidProof();
|
|
51
117
|
|
|
52
118
|
(bool success, bytes memory result) = implementation.delegatecall(
|
|
53
119
|
abi.encodeCall(ICounterfactualImplementation.execute, (params, submitterData))
|
|
@@ -58,4 +124,19 @@ contract CounterfactualDeposit is ICounterfactualDeposit {
|
|
|
58
124
|
}
|
|
59
125
|
}
|
|
60
126
|
}
|
|
127
|
+
|
|
128
|
+
function _updateRoot(bytes32 newRoot, bytes32[] calldata proof) private {
|
|
129
|
+
CounterfactualStorage storage $ = _getStorage();
|
|
130
|
+
if (newRoot == $.activeRoot) revert RootUnchanged();
|
|
131
|
+
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(address(this), newRoot))));
|
|
132
|
+
if (!MerkleProof.verify(proof, BEACON.upgradeRoot(), leaf)) revert InvalidUpgradeProof();
|
|
133
|
+
$.activeRoot = newRoot;
|
|
134
|
+
emit RootUpdated(newRoot);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function _getStorage() private pure returns (CounterfactualStorage storage $) {
|
|
138
|
+
assembly {
|
|
139
|
+
$.slot := STORAGE_LOCATION
|
|
140
|
+
}
|
|
141
|
+
}
|
|
61
142
|
}
|