diff --git a/docs/primitives/catalog.json b/docs/primitives/catalog.json index e7eccaf..0598f90 100644 --- a/docs/primitives/catalog.json +++ b/docs/primitives/catalog.json @@ -2,7 +2,7 @@ "generatedBy": "script/docs/gen-primitives.mjs", "note": "Generated from the script/ tree and the Makefile. Do not edit by hand; run `npm run docs:catalog`.", "counts": { - "primitives": 60, + "primitives": 61, "makeTargets": 27 }, "primitives": [ @@ -239,7 +239,8 @@ "writes_onchain": true, "destructive": false, "inputs": [ - "DEST_CHAIN" + "DEST_CHAIN", + "DEST_CHAIN_SELECTOR" ] }, { @@ -285,7 +286,8 @@ "writes_onchain": false, "destructive": false, "inputs": [ - "DEST_CHAIN" + "DEST_CHAIN", + "DEST_CHAIN_SELECTOR" ] }, { @@ -302,6 +304,7 @@ "destructive": false, "inputs": [ "DEST_CHAIN", + "DEST_CHAIN_SELECTOR", "DISABLE" ] }, @@ -331,7 +334,8 @@ "writes_onchain": true, "destructive": false, "inputs": [ - "DEST_CHAIN" + "DEST_CHAIN", + "DEST_CHAIN_SELECTOR" ] }, { @@ -421,6 +425,7 @@ "destructive": false, "inputs": [ "DEST_CHAIN", + "DEST_CHAIN_SELECTOR", "FAST_FINALITY" ] }, @@ -438,6 +443,7 @@ "destructive": false, "inputs": [ "DEST_CHAIN", + "DEST_CHAIN_SELECTOR", "FAST_FINALITY" ] }, @@ -454,7 +460,9 @@ "writes_onchain": true, "destructive": true, "inputs": [ - "DEST_CHAIN" + "DEST_CHAIN", + "DEST_CHAIN_FAMILY", + "DEST_CHAIN_SELECTOR" ] }, { @@ -471,6 +479,8 @@ "destructive": false, "inputs": [ "DEST_CHAIN", + "DEST_CHAIN_FAMILY", + "DEST_CHAIN_SELECTOR", "REMOTE_POOL_ADDRESS" ] }, @@ -486,7 +496,26 @@ "writes_onchain": false, "destructive": false, "inputs": [ - "DEST_CHAIN" + "DEST_CHAIN", + "DEST_CHAIN_FAMILY", + "DEST_CHAIN_SELECTOR" + ] + }, + { + "name": "GetRemoteToken", + "script": "script/configure/remote-pools/GetRemoteToken.s.sol", + "group": "remote-pools", + "description": "Reads and displays the remote token configured on a TokenPool for a given remote chain.", + "modes": [ + "read" + ], + "read_only": true, + "writes_onchain": false, + "destructive": false, + "inputs": [ + "DEST_CHAIN", + "DEST_CHAIN_FAMILY", + "DEST_CHAIN_SELECTOR" ] }, { @@ -503,6 +532,8 @@ "destructive": true, "inputs": [ "DEST_CHAIN", + "DEST_CHAIN_FAMILY", + "DEST_CHAIN_SELECTOR", "REMOTE_POOL_ADDRESS" ] }, diff --git a/docs/primitives/ccv/UpdateCCVConfig.md b/docs/primitives/ccv/UpdateCCVConfig.md index 4c6de70..1253523 100644 --- a/docs/primitives/ccv/UpdateCCVConfig.md +++ b/docs/primitives/ccv/UpdateCCVConfig.md @@ -18,6 +18,7 @@ Applies CCV (Cross-Chain Verifier) configuration to a token pool's AdvancedPoolH | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | ## Reference diff --git a/docs/primitives/fee-config/GetTokenTransferFeeConfig.md b/docs/primitives/fee-config/GetTokenTransferFeeConfig.md index e0fc965..056f07c 100644 --- a/docs/primitives/fee-config/GetTokenTransferFeeConfig.md +++ b/docs/primitives/fee-config/GetTokenTransferFeeConfig.md @@ -18,6 +18,7 @@ Reads and displays the token transfer fee configuration for a token pool on a gi | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | ## Reference diff --git a/docs/primitives/fee-config/UpdateTokenTransferFeeConfig.md b/docs/primitives/fee-config/UpdateTokenTransferFeeConfig.md index dfd2008..fb47856 100644 --- a/docs/primitives/fee-config/UpdateTokenTransferFeeConfig.md +++ b/docs/primitives/fee-config/UpdateTokenTransferFeeConfig.md @@ -18,6 +18,7 @@ Applies token transfer fee configuration updates to a token pool on a given dest | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | | `DISABLE` | See the script header. | ## Reference diff --git a/docs/primitives/finality-config/SetFinalityConfig.md b/docs/primitives/finality-config/SetFinalityConfig.md index 0966839..0ec9946 100644 --- a/docs/primitives/finality-config/SetFinalityConfig.md +++ b/docs/primitives/finality-config/SetFinalityConfig.md @@ -18,6 +18,7 @@ Sets the allowed finality configuration on a TokenPool, and optionally updates r | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | ## Reference diff --git a/docs/primitives/index.md b/docs/primitives/index.md index 5ef3006..c40176b 100644 --- a/docs/primitives/index.md +++ b/docs/primitives/index.md @@ -105,6 +105,7 @@ at [`catalog.json`](catalog.json). These pages are generated from the scripts an - [AddRemotePool](remote-pools/AddRemotePool.md) - Adds a remote pool address to a TokenPool for a given remote chain. _(write)_ - [GetRemotePools](remote-pools/GetRemotePools.md) - Reads and displays the remote pool addresses configured on a TokenPool for a given remote chain. _(read-only)_ +- [GetRemoteToken](remote-pools/GetRemoteToken.md) - Reads and displays the remote token configured on a TokenPool for a given remote chain. _(read-only)_ - [RemoveRemotePool](remote-pools/RemoveRemotePool.md) - Removes a remote pool address from a TokenPool for a given remote chain. _(write, destructive)_ ## token-admin-registry diff --git a/docs/primitives/rate-limiter/GetCurrentRateLimits.md b/docs/primitives/rate-limiter/GetCurrentRateLimits.md index ce8a743..943538a 100644 --- a/docs/primitives/rate-limiter/GetCurrentRateLimits.md +++ b/docs/primitives/rate-limiter/GetCurrentRateLimits.md @@ -18,6 +18,7 @@ Reads and displays the current rate limiter state for a TokenPool, compatible wi | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | | `FAST_FINALITY` | See the script header. | ## Reference diff --git a/docs/primitives/rate-limiter/UpdateRateLimiters.md b/docs/primitives/rate-limiter/UpdateRateLimiters.md index 0a49b24..386a430 100644 --- a/docs/primitives/rate-limiter/UpdateRateLimiters.md +++ b/docs/primitives/rate-limiter/UpdateRateLimiters.md @@ -18,6 +18,7 @@ Updates rate limiter configuration on a TokenPool, compatible with both v1 and v | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | | `FAST_FINALITY` | See the script header. | ## Reference diff --git a/docs/primitives/remote-chains/RemoveChain.md b/docs/primitives/remote-chains/RemoveChain.md index 9e4caad..01f06de 100644 --- a/docs/primitives/remote-chains/RemoveChain.md +++ b/docs/primitives/remote-chains/RemoveChain.md @@ -18,6 +18,8 @@ Fully unsupports a remote chain on the source TokenPool: removes the chain selec | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_FAMILY` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | ## Reference diff --git a/docs/primitives/remote-pools/AddRemotePool.md b/docs/primitives/remote-pools/AddRemotePool.md index 474347f..af85e1e 100644 --- a/docs/primitives/remote-pools/AddRemotePool.md +++ b/docs/primitives/remote-pools/AddRemotePool.md @@ -18,6 +18,8 @@ Adds a remote pool address to a TokenPool for a given remote chain. | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_FAMILY` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | | `REMOTE_POOL_ADDRESS` | See the script header. | ## Reference diff --git a/docs/primitives/remote-pools/GetRemotePools.md b/docs/primitives/remote-pools/GetRemotePools.md index 59fe7ca..b128eed 100644 --- a/docs/primitives/remote-pools/GetRemotePools.md +++ b/docs/primitives/remote-pools/GetRemotePools.md @@ -18,6 +18,8 @@ Reads and displays the remote pool addresses configured on a TokenPool for a giv | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_FAMILY` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | ## Reference diff --git a/docs/primitives/remote-pools/GetRemoteToken.md b/docs/primitives/remote-pools/GetRemoteToken.md new file mode 100644 index 0000000..519abec --- /dev/null +++ b/docs/primitives/remote-pools/GetRemoteToken.md @@ -0,0 +1,32 @@ +--- +name: GetRemoteToken +script: script/configure/remote-pools/GetRemoteToken.s.sol +group: remote-pools +type: reference +modes: [read] +read_only: true +writes_onchain: false +destructive: false +--- + +# GetRemoteToken + +Reads and displays the remote token configured on a TokenPool for a given remote chain. + +## Inputs + +| Env var | Description | +| --- | --- | +| `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_FAMILY` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | + +## Reference + +- Script: [`script/configure/remote-pools/GetRemoteToken.s.sol`](../../../script/configure/remote-pools/GetRemoteToken.s.sol) +- Modes: read +- Read-only: true | Writes on-chain: false | Destructive: false + +_This page is generated from the script by `script/docs/gen-primitives.mjs`. Edit the script's +`@notice` for the description, or `docs/primitives/_meta.json` for the authored context; do not edit +this file by hand._ diff --git a/docs/primitives/remote-pools/RemoveRemotePool.md b/docs/primitives/remote-pools/RemoveRemotePool.md index e2e22eb..9dc041a 100644 --- a/docs/primitives/remote-pools/RemoveRemotePool.md +++ b/docs/primitives/remote-pools/RemoveRemotePool.md @@ -18,6 +18,8 @@ Removes a remote pool address from a TokenPool for a given remote chain. | Env var | Description | | --- | --- | | `DEST_CHAIN` | See the script header. | +| `DEST_CHAIN_FAMILY` | See the script header. | +| `DEST_CHAIN_SELECTOR` | See the script header. | | `REMOTE_POOL_ADDRESS` | See the script header. | ## Reference diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index f4ba2f3..18dd020 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -459,6 +459,30 @@ contract HelperConfig is Script { return "Unknown"; } + /// @notice Resolves a NetworkConfig by chain selector directly - the selector-keyed analog of + /// `getDestChainConfig(string)`. Use this when you have a selector (e.g. from + /// `getSupportedChains()`) and need the chain's family/name, NOT the display name that + /// `getChainNameBySelector` returns: `getDestChainConfig` matches the chainNameIdentifier + /// (e.g. "SOLANA_DEVNET"), not the displayName ("Solana Devnet"), so the round-trip + /// selector -> displayName -> getDestChainConfig misses for non-EVM chains and falls back + /// to a zero (evm) config. This function avoids that round-trip. + /// @dev Returns a zero config (chainFamily = "") for an unrecognized selector, mirroring + /// `getDestChainConfig`'s contract for unknown names. + function getDestChainConfigBySelector(uint64 chainSelector) public view returns (NetworkConfig memory) { + if (chainSelector == getEthereumSepoliaConfig().chainSelector) return getEthereumSepoliaConfig(); + if (chainSelector == getZeroGTestnetConfig().chainSelector) return getZeroGTestnetConfig(); + if (chainSelector == getPlumeTestnetConfig().chainSelector) return getPlumeTestnetConfig(); + if (chainSelector == getInkSepoliaConfig().chainSelector) return getInkSepoliaConfig(); + if (chainSelector == getMantleSepoliaConfig().chainSelector) return getMantleSepoliaConfig(); + if (chainSelector == getSolanaDevnetConfig().chainSelector) return getSolanaDevnetConfig(); + // Fallback: the discovered-chain cache. + for (uint256 i = 0; i < s_chains.length; i++) { + if (s_chains[i].chainSelector == chainSelector) return s_chains[i]; + } + NetworkConfig memory unknown; + return unknown; + } + function getNativeCurrencySymbol(uint256 chainId) public view returns (string memory) { return getNetworkConfig(chainId).nativeCurrencySymbol; } diff --git a/script/configure/ccv/UpdateCCVConfig.s.sol b/script/configure/ccv/UpdateCCVConfig.s.sol index 2e85585..b409e41 100644 --- a/script/configure/ccv/UpdateCCVConfig.s.sol +++ b/script/configure/ccv/UpdateCCVConfig.s.sol @@ -33,7 +33,11 @@ import {ProjectStore} from "../../../src/utils/ProjectStore.sol"; /// resolves and displays it and MUST be broadcast from that account. /// /// Environment Variables: -/// DEST_CHAIN - the remote lane to configure CCVs for (optional; omit for threshold-only) +/// DEST_CHAIN - the remote lane to configure CCVs for (optional; omit for threshold-only). +/// Accepts EVM and non-EVM destinations (e.g. MANTLE_SEPOLIA, +/// SOLANA_DEVNET, APTOS_TESTNET). +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// OUTBOUND_CCVS - comma-separated address list: base CCVs for outbound messages /// THRESHOLD_OUTBOUND_CCVS - comma-separated address list: extra outbound CCVs at/above the threshold /// INBOUND_CCVS - comma-separated address list: base CCVs for inbound messages @@ -130,9 +134,16 @@ contract UpdateCCVConfig is EoaExecutor, LanePolicySource { _header(chainName, tokenPoolAddress, hooksAddress, hooksOwner, destChainName, haveLane); // ── Read current on-chain state (RMW baseline) ───────────────────── - uint64 destChainSelector = haveLane - ? helperConfig.getNetworkConfig(helperConfig.parseChainName(destChainName)).chainSelector - : uint64(0); + uint64 destChainSelector; + if (haveLane) { + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + destChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + destChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + } else { + destChainSelector = uint64(0); + } AdvancedPoolHooks.CCVConfig memory currentConfig = haveLane ? AdvancedPoolHooks(hooksAddress).getCCVConfig(destChainSelector) : _emptyConfig(); uint256 currentThreshold = AdvancedPoolHooks(hooksAddress).getThresholdAmount(); diff --git a/script/configure/fee-config/GetTokenTransferFeeConfig.s.sol b/script/configure/fee-config/GetTokenTransferFeeConfig.s.sol index 785cdd1..a0d345e 100644 --- a/script/configure/fee-config/GetTokenTransferFeeConfig.s.sol +++ b/script/configure/fee-config/GetTokenTransferFeeConfig.s.sol @@ -13,7 +13,12 @@ import {IPoolV2} from "@chainlink/contracts-ccip/contracts/interfaces/IPoolV2.so /// If the pool does not support this function, the script will revert with an informative message. /// /// Environment Variables (required): -/// DEST_CHAIN - The remote destination chain whose fee config is being queried (e.g. MANTLE_SEPOLIA) +/// DEST_CHAIN - The remote destination chain whose fee config is being queried +/// (e.g. MANTLE_SEPOLIA, SOLANA_DEVNET, APTOS_TESTNET) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Usage example: /// DEST_CHAIN=MANTLE_SEPOLIA \ @@ -26,12 +31,15 @@ contract GetTokenTransferFeeConfig is Script { // ── Required env vars ────────────────────────────────────────────── string memory destChainName = vm.envString("DEST_CHAIN"); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 chainId = block.chainid; string memory chainName = helperConfig.getChainName(chainId); - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 destChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + uint64 destChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require(destChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR."); + string memory destChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(chainId); @@ -50,7 +58,7 @@ contract GetTokenTransferFeeConfig is Script { console.log(unicode"💰 Get Token Transfer Fee Config"); console.log("========================================"); console.log(string.concat("Chain: ", chainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", destChainDisplayName)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "View fee config")); console.log("========================================"); diff --git a/script/configure/fee-config/UpdateTokenTransferFeeConfig.s.sol b/script/configure/fee-config/UpdateTokenTransferFeeConfig.s.sol index df58264..65d5631 100644 --- a/script/configure/fee-config/UpdateTokenTransferFeeConfig.s.sol +++ b/script/configure/fee-config/UpdateTokenTransferFeeConfig.s.sol @@ -22,7 +22,12 @@ import {ProjectStore} from "../../../src/utils/ProjectStore.sol"; /// To disable the fee config (reverting to FeeQuoter defaults), set DISABLE=true. /// /// Environment Variables (required): -/// DEST_CHAIN - The remote destination chain to configure fees for (e.g. MANTLE_SEPOLIA) +/// DEST_CHAIN - The remote destination chain to configure fees for +/// (e.g. MANTLE_SEPOLIA, SOLANA_DEVNET, APTOS_TESTNET) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Environment Variables (per-field, optional when DISABLE is false or unset - see the ladder below): /// DEST_GAS_OVERHEAD - uint32, gas overhead charged on destination chain (must be > 0) @@ -109,13 +114,16 @@ contract UpdateTokenTransferFeeConfig is EoaExecutor, LanePolicySource { string memory destChainName = vm.envString("DEST_CHAIN"); bool disable = vm.envOr("DISABLE", false); - // ── Resolve chain IDs, selectors ──────────────────────────────── + // ── Resolve selector from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 chainId = block.chainid; string memory chainName = helperConfig.getChainName(chainId); - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 destChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + uint64 destChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require(destChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR."); + string memory destChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(chainId); @@ -142,7 +150,7 @@ contract UpdateTokenTransferFeeConfig is EoaExecutor, LanePolicySource { console.log(unicode"💰 Update Token Transfer Fee Config"); console.log("========================================"); console.log(string.concat("Chain: ", chainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", destChainDisplayName)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", disable ? "Disable fee config" : "Set fee config")); console.log("========================================"); @@ -156,7 +164,7 @@ contract UpdateTokenTransferFeeConfig is EoaExecutor, LanePolicySource { tokenPool.isSupportedChain(destChainSelector), string.concat( "Destination chain ", - helperConfig.getChainName(destChainId), + destChainDisplayName, " (selector: ", vm.toString(destChainSelector), ") is not configured on this pool. Run ApplyChainUpdates first." @@ -166,9 +174,7 @@ contract UpdateTokenTransferFeeConfig is EoaExecutor, LanePolicySource { FeeConfigResolution memory res; if (disable) { // ── Disable fee config for this lane ────────────────────────── - console.log( - string.concat("[Step 1] Disabling fee config for lane to ", helperConfig.getChainName(destChainId)) - ); + console.log(string.concat("[Step 1] Disabling fee config for lane to ", destChainDisplayName)); uint64[] memory toDisable = new uint64[](1); toDisable[0] = destChainSelector; @@ -224,9 +230,7 @@ contract UpdateTokenTransferFeeConfig is EoaExecutor, LanePolicySource { TokenPool.TokenTransferFeeConfigArgs[] memory args = _buildFeeConfigArgs(res, destChainSelector); uint64[] memory emptyDisable = new uint64[](0); - console.log( - string.concat("[Step 1] Applying fee config for lane to ", helperConfig.getChainName(destChainId)) - ); + console.log(string.concat("[Step 1] Applying fee config for lane to ", destChainDisplayName)); // applyTokenTransferFeeConfigUpdates() was introduced in TokenPool v2.0. // On v1 pools, fee configuration is handled by FeeQuoter and requires diff --git a/script/configure/finality-config/SetFinalityConfig.s.sol b/script/configure/finality-config/SetFinalityConfig.s.sol index 79a447c..ee40ba4 100644 --- a/script/configure/finality-config/SetFinalityConfig.s.sol +++ b/script/configure/finality-config/SetFinalityConfig.s.sol @@ -39,9 +39,11 @@ import {ProjectStore} from "../../../src/utils/ProjectStore.sol"; /// hand edit). The declaration is owner intent: an env-driven apply never writes it back. /// /// Environment Variables (optional - rate limiter): -/// DEST_CHAIN - Remote chain whose lane is queried/updated (e.g. MANTLE_SEPOLIA). -/// Required when any rate limit variable is set; if omitted the rate -/// limiter section is skipped entirely. +/// DEST_CHAIN - Remote chain whose lane is queried/updated (e.g. MANTLE_SEPOLIA, +/// SOLANA_DEVNET, APTOS_TESTNET). Required when any rate limit +/// variable is set; if omitted the rate limiter section is skipped. +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// OUTBOUND_RATE_LIMIT_CAPACITY - uint128, outbound token bucket capacity /// OUTBOUND_RATE_LIMIT_RATE - uint128, outbound token bucket refill rate /// OUTBOUND_RATE_LIMIT_ENABLED - true/false (defaults to true when CAPACITY or RATE are set) @@ -191,9 +193,13 @@ contract SetFinalityConfig is EoaExecutor, LanePolicySource { uint64 remoteChainSelector; string memory destChainFullName; if (destChainSet) { - uint256 destChainId = helperConfig.parseChainName(destChainName); - remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; - destChainFullName = helperConfig.getChainName(destChainId); + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, + "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + destChainFullName = bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; } // ── Header ───────────────────────────────────────────────────────── diff --git a/script/configure/rate-limiter/GetCurrentRateLimits.s.sol b/script/configure/rate-limiter/GetCurrentRateLimits.s.sol index 72a621c..af35e9a 100644 --- a/script/configure/rate-limiter/GetCurrentRateLimits.s.sol +++ b/script/configure/rate-limiter/GetCurrentRateLimits.s.sol @@ -11,11 +11,14 @@ import {PoolVersions} from "../../../src/PoolVersions.sol"; /// @notice Reads and displays the current rate limiter state for a TokenPool, compatible with v1 and v2 pools. /// /// Environment Variables (required): -/// DEST_CHAIN - The remote chain whose lane is being queried (e.g. MANTLE_SEPOLIA) +/// DEST_CHAIN - The remote chain whose lane is being queried (e.g. MANTLE_SEPOLIA, +/// SOLANA_DEVNET, APTOS_TESTNET) /// -/// Environment Variables (optional, v2 only): -/// FAST_FINALITY - true/false, whether to read the fast finality bucket -/// (default: false, reads the standard finality bucket) +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) +/// FAST_FINALITY - true/false, whether to read the fast finality bucket (v2 only; +/// default: false, reads the standard finality bucket) /// /// Usage example: /// DEST_CHAIN=MANTLE_SEPOLIA \ @@ -31,12 +34,17 @@ contract GetCurrentRateLimits is Script { // ── Optional env vars ────────────────────────────────────────────── bool fastFinality = vm.envOr("FAST_FINALITY", false); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 chainId = block.chainid; string memory chainName = helperConfig.getChainName(chainId); - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + string memory destChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(chainId); @@ -55,7 +63,7 @@ contract GetCurrentRateLimits is Script { console.log(unicode"📊 Get Current Rate Limits"); console.log("========================================"); console.log(string.concat("Chain: ", chainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", destChainDisplayName)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "View rate limits")); if (fastFinality) { diff --git a/script/configure/rate-limiter/UpdateRateLimiters.s.sol b/script/configure/rate-limiter/UpdateRateLimiters.s.sol index af18604..650b9a4 100644 --- a/script/configure/rate-limiter/UpdateRateLimiters.s.sol +++ b/script/configure/rate-limiter/UpdateRateLimiters.s.sol @@ -22,7 +22,12 @@ import {ProjectStore} from "../../../src/utils/ProjectStore.sol"; /// two sources. /// /// Environment Variables (required): -/// DEST_CHAIN - The remote chain whose rate limit lane is being updated (e.g. MANTLE_SEPOLIA) +/// DEST_CHAIN - The remote chain whose rate limit lane is being updated +/// (e.g. MANTLE_SEPOLIA, SOLANA_DEVNET, APTOS_TESTNET) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Environment Variables (set to update outbound - any one triggers the direction): /// OUTBOUND_RATE_LIMIT_CAPACITY - uint128, token bucket capacity (isEnabled defaults to true when set) @@ -128,11 +133,14 @@ contract UpdateRateLimiters is EoaExecutor, LanePolicySource { // isEnabled defaults to true when CAPACITY or RATE are provided. RateLimiterUtils.RateLimitUpdate memory envUpdate = _readRateLimitUpdate(); - // ── Resolve chain IDs / selectors, store in contract storage ─────── + // ── Resolve selector from destination config (EVM and non-EVM), store in contract storage ── helperConfig = new HelperConfig(); s_chainId = block.chainid; - uint256 destChainId = helperConfig.parseChainName(destChainName); - s_selector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + s_selector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require(s_selector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR."); + string memory destChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool, detect version ─────────────────────────────────── s_poolAddress = helperConfig.getDeployedTokenPool(s_chainId); @@ -160,7 +168,7 @@ contract UpdateRateLimiters is EoaExecutor, LanePolicySource { console.log(unicode"⚡️ Update Rate Limiters"); console.log("========================================"); console.log(string.concat("Chain: ", helperConfig.getChainName(s_chainId))); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", destChainDisplayName)); console.log(string.concat("Token Pool: ", vm.toString(s_poolAddress))); console.log(string.concat("Action: ", "Update rate limits")); console.log( diff --git a/script/configure/remote-chains/RemoveChain.s.sol b/script/configure/remote-chains/RemoveChain.s.sol index ac4a7fc..9a549d9 100644 --- a/script/configure/remote-chains/RemoveChain.s.sol +++ b/script/configure/remote-chains/RemoveChain.s.sol @@ -9,6 +9,8 @@ import {PoolVersion} from "../../utils/PoolVersion.s.sol"; import {PoolVersions} from "../../../src/PoolVersions.sol"; import {CctActions, ITokenPoolV150} from "../../../src/actions/CctActions.sol"; import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; +import {AddressEncoding} from "../../utils/AddressEncoding.s.sol"; +import {ChainHandlers} from "../../utils/ChainHandlers.s.sol"; /// @notice Fully unsupports a remote chain on the source TokenPool: removes the chain selector and /// deletes its remote-chain config (pools, remote token, rate limits). After this call @@ -28,7 +30,11 @@ import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; /// are no inflight messages to or from this chain before removing it to avoid loss of funds. /// /// Environment Variables (required): -/// DEST_CHAIN - The remote chain to unsupport (e.g. MANTLE_SEPOLIA) +/// DEST_CHAIN - The remote chain to unsupport (e.g. MANTLE_SEPOLIA, SOLANA_DEVNET, APTOS_TESTNET) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Usage example: /// DEST_CHAIN=MANTLE_SEPOLIA \ @@ -40,11 +46,20 @@ contract RemoveChain is EoaExecutor { // ── Required env vars ────────────────────────────────────────────── string memory destChainName = vm.envString("DEST_CHAIN"); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector/family from destination config (EVM and non-EVM) ── helperConfig = new HelperConfig(); uint256 sourceChainId = block.chainid; - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + string memory destChainFamilyStr = vm.envOr( + "DEST_CHAIN_FAMILY", bytes(destConfig.chainFamily).length > 0 ? destConfig.chainFamily : string("evm") + ); + ChainHandlers.ChainFamily destChainFamily = ChainHandlers._parseChainFamily(destChainFamilyStr); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + string memory destChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(sourceChainId); @@ -57,7 +72,7 @@ contract RemoveChain is EoaExecutor { ) ); - _removeChain(tokenPoolAddress, remoteChainSelector, destChainName, destChainId); + _removeChain(tokenPoolAddress, remoteChainSelector, destChainName, destChainDisplayName, destChainFamily); } /// @dev Everything after input resolution. Split from run() so the version dispatch is testable @@ -67,7 +82,8 @@ contract RemoveChain is EoaExecutor { address tokenPoolAddress, uint64 remoteChainSelector, string memory destChainName, - uint256 destChainId + string memory destChainDisplayName, + ChainHandlers.ChainFamily destChainFamily ) internal { uint256 sourceChainId = block.chainid; string memory sourceChainName = helperConfig.getChainName(sourceChainId); @@ -87,7 +103,7 @@ contract RemoveChain is EoaExecutor { console.log(unicode"➖ Remove Remote Chain"); console.log("========================================"); console.log(string.concat("Chain: ", sourceChainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", destChainDisplayName)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "Unsupport remote chain (full lane teardown)")); console.log("========================================"); @@ -111,7 +127,15 @@ contract RemoveChain is EoaExecutor { bytes[] memory currentPools = PoolVersion._remotePools(tokenPoolAddress, poolVersion, remoteChainSelector); console.log(string.concat("Current Remote Pools on this lane: ", vm.toString(currentPools.length))); for (uint256 i = 0; i < currentPools.length; i++) { - if (currentPools[i].length == 32) { + // Family-aware decode: only an EVM destination's 32-byte ABI word (high 12 bytes zero) + // is abi.decode'd to an address. A Solana pubkey or an Aptos address is also 32 bytes, + // and a short-form Aptos address (e.g. 0x1) left-pads to 12+ leading zeros and would be + // silently misdecoded as a truncated EVM address by the heuristic alone - so gate on the + // resolved family first, exactly as the remote-pools scripts do. + if ( + destChainFamily == ChainHandlers.ChainFamily.EVM + && AddressEncoding._isAbiEncodedAddress(currentPools[i]) + ) { console.log( string.concat(" [", vm.toString(i), "] ", vm.toString(abi.decode(currentPools[i], (address)))) ); @@ -131,7 +155,7 @@ contract RemoveChain is EoaExecutor { console.log(string.concat(unicode"✅ Complete on ", sourceChainName, "!")); console.log("========================================"); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); - console.log(string.concat("Removed Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Removed Chain: ", destChainDisplayName)); console.log(string.concat("Selector: ", vm.toString(remoteChainSelector))); console.log( string.concat( @@ -142,6 +166,24 @@ contract RemoveChain is EoaExecutor { console.log(""); } + /// @dev Backward-compat overload kept for test harnesses and older internal call sites. + /// Resolves the display name from the EVM chain ID and delegates to the family-aware path. + /// The family defaults to EVM (the historical callers are EVM-only test harnesses). + function _removeChain( + address tokenPoolAddress, + uint64 remoteChainSelector, + string memory destChainName, + uint256 destChainId + ) internal { + _removeChain( + tokenPoolAddress, + remoteChainSelector, + destChainName, + helperConfig.getChainName(destChainId), + ChainHandlers.ChainFamily.EVM + ); + } + /// @dev The exhaustive version switch for a whole-chain removal, matching ApplyChainUpdates' /// lane-update dispatch: 1.5.0 takes the single-argument encoding with one `allowed:false` /// entry, every later cataloged version takes the modern (removes[], adds[]) encoding with diff --git a/script/configure/remote-pools/AddRemotePool.s.sol b/script/configure/remote-pools/AddRemotePool.s.sol index 456f024..d5ce4fd 100644 --- a/script/configure/remote-pools/AddRemotePool.s.sol +++ b/script/configure/remote-pools/AddRemotePool.s.sol @@ -8,6 +8,8 @@ import {PoolVersion} from "../../utils/PoolVersion.s.sol"; import {PoolVersions} from "../../../src/PoolVersions.sol"; import {CctActions} from "../../../src/actions/CctActions.sol"; import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; +import {ChainHandlers} from "../../utils/ChainHandlers.s.sol"; +import {AddressEncoding} from "../../utils/AddressEncoding.s.sol"; /// @notice Adds a remote pool address to a TokenPool for a given remote chain. /// @@ -18,7 +20,12 @@ import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; /// /// Environment Variables (required): /// DEST_CHAIN - The remote chain where the new pool was deployed (e.g. MANTLE_SEPOLIA) -/// REMOTE_POOL_ADDRESS - The address of the new remote pool to add +/// REMOTE_POOL_ADDRESS - The address of the new remote pool to add (EVM: 0x address; +/// SVM: base58; Aptos: 0x hex) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Usage example: /// DEST_CHAIN=MANTLE_SEPOLIA \ @@ -30,13 +37,29 @@ contract AddRemotePool is EoaExecutor { function run() external { // ── Required env vars ────────────────────────────────────────────── string memory destChainName = vm.envString("DEST_CHAIN"); - address remotePoolAddress = vm.envAddress("REMOTE_POOL_ADDRESS"); + string memory remotePoolAddressRaw = vm.envString("REMOTE_POOL_ADDRESS"); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector/family from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 sourceChainId = block.chainid; - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + string memory destChainFamilyStr = vm.envOr( + "DEST_CHAIN_FAMILY", bytes(destConfig.chainFamily).length > 0 ? destConfig.chainFamily : string("evm") + ); + ChainHandlers.ChainFamily destChainFamily = ChainHandlers._parseChainFamily(destChainFamilyStr); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + string memory remoteChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; + + require( + ChainHandlers._validateChainAddress(remotePoolAddressRaw, destChainFamily), + string.concat("Invalid ", destChainFamilyStr, " REMOTE_POOL_ADDRESS: ", remotePoolAddressRaw) + ); + bytes memory remotePoolAddressEncoded = + ChainHandlers._prepareChainAddressData(remotePoolAddressRaw, destChainFamily); // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(sourceChainId); @@ -49,7 +72,15 @@ contract AddRemotePool is EoaExecutor { ) ); - _addRemotePool(tokenPoolAddress, remotePoolAddress, remoteChainSelector, destChainName, destChainId); + _addRemotePool( + tokenPoolAddress, + remotePoolAddressRaw, + remotePoolAddressEncoded, + remoteChainSelector, + remoteChainDisplayName, + destChainFamilyStr, + destChainFamily + ); } /// @dev Everything after input resolution, starting with the version fence. Split from run() @@ -58,10 +89,12 @@ contract AddRemotePool is EoaExecutor { /// test suites). function _addRemotePool( address tokenPoolAddress, - address remotePoolAddress, + string memory remotePoolAddressRaw, + bytes memory remotePoolAddressEncoded, uint64 remoteChainSelector, - string memory destChainName, - uint256 destChainId + string memory remoteChainDisplayName, + string memory destChainFamilyStr, + ChainHandlers.ChainFamily destChainFamily ) internal { uint256 sourceChainId = block.chainid; string memory sourceChainName = helperConfig.getChainName(sourceChainId); @@ -77,7 +110,7 @@ contract AddRemotePool is EoaExecutor { tokenPool.isSupportedChain(remoteChainSelector), string.concat( "Remote chain not supported. Run ApplyChainUpdates first to add ", - destChainName, + remoteChainDisplayName, " as a supported chain." ) ); @@ -88,19 +121,23 @@ contract AddRemotePool is EoaExecutor { console.log(unicode"➕ Add Remote Pool"); console.log("========================================"); console.log(string.concat("Chain: ", sourceChainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Remote Family:", " ", destChainFamilyStr)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "Add remote pool")); console.log("========================================"); console.log(""); - console.log(string.concat("New Remote Pool: ", vm.toString(remotePoolAddress))); + console.log(string.concat("New Remote Pool: ", remotePoolAddressRaw)); console.log(""); // ── Show current remote pools ────────────────────────────────────── bytes[] memory currentPools = tokenPool.getRemotePools(remoteChainSelector); console.log(string.concat("Current Remote Pools: ", vm.toString(currentPools.length))); for (uint256 i = 0; i < currentPools.length; i++) { - if (currentPools[i].length == 32) { + if ( + destChainFamily == ChainHandlers.ChainFamily.EVM + && AddressEncoding._isAbiEncodedAddress(currentPools[i]) + ) { console.log( string.concat(" [", vm.toString(i), "] ", vm.toString(abi.decode(currentPools[i], (address)))) ); @@ -112,7 +149,7 @@ contract AddRemotePool is EoaExecutor { console.log(string.concat("[Step 1] Adding remote pool on ", sourceChainName)); - _executeCalls(CctActions._addRemotePool(tokenPoolAddress, remoteChainSelector, abi.encode(remotePoolAddress))); + _executeCalls(CctActions._addRemotePool(tokenPoolAddress, remoteChainSelector, remotePoolAddressEncoded)); console.log(unicode"✅ Remote pool added successfully!"); console.log(""); @@ -120,8 +157,8 @@ contract AddRemotePool is EoaExecutor { console.log(string.concat(unicode"✅ Complete on ", sourceChainName, "!")); console.log("========================================"); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); - console.log(string.concat("Added Pool: ", vm.toString(remotePoolAddress))); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Added Pool: ", remotePoolAddressRaw)); console.log( string.concat( "Token Pool: ", helperConfig.getExplorerUrl(sourceChainId, "/address/", tokenPoolAddress) @@ -130,4 +167,24 @@ contract AddRemotePool is EoaExecutor { console.log("========================================"); console.log(""); } + + /// @dev Backward-compat overload kept for test harnesses and older internal call sites. + /// Interprets `remotePoolAddress` as an EVM address and delegates to the family-aware path. + function _addRemotePool( + address tokenPoolAddress, + address remotePoolAddress, + uint64 remoteChainSelector, + string memory, + uint256 destChainId + ) internal { + _addRemotePool( + tokenPoolAddress, + vm.toString(remotePoolAddress), + abi.encode(remotePoolAddress), + remoteChainSelector, + helperConfig.getChainName(destChainId), + "evm", + ChainHandlers.ChainFamily.EVM + ); + } } diff --git a/script/configure/remote-pools/GetRemotePools.s.sol b/script/configure/remote-pools/GetRemotePools.s.sol index c9d2b3d..090392f 100644 --- a/script/configure/remote-pools/GetRemotePools.s.sol +++ b/script/configure/remote-pools/GetRemotePools.s.sol @@ -6,6 +6,8 @@ import {HelperConfig} from "../../HelperConfig.s.sol"; import {TokenPool} from "@chainlink/contracts-ccip/contracts/pools/TokenPool.sol"; import {PoolVersion} from "../../utils/PoolVersion.s.sol"; import {PoolVersions} from "../../../src/PoolVersions.sol"; +import {ChainHandlers} from "../../utils/ChainHandlers.s.sol"; +import {AddressEncoding} from "../../utils/AddressEncoding.s.sol"; /// @notice Reads and displays the remote pool addresses configured on a TokenPool for a given remote chain. /// @@ -23,12 +25,22 @@ contract GetRemotePools is Script { // ── Required env vars ────────────────────────────────────────────── string memory destChainName = vm.envString("DEST_CHAIN"); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 sourceChainId = block.chainid; - uint256 destChainId = helperConfig.parseChainName(destChainName); + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + string memory destChainFamilyStr = vm.envOr( + "DEST_CHAIN_FAMILY", bytes(destConfig.chainFamily).length > 0 ? destConfig.chainFamily : string("evm") + ); + ChainHandlers.ChainFamily destChainFamily = ChainHandlers._parseChainFamily(destChainFamilyStr); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + string memory sourceChainName = helperConfig.getChainName(sourceChainId); - uint64 remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + string memory remoteChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(sourceChainId); @@ -47,7 +59,8 @@ contract GetRemotePools is Script { console.log(unicode"🏊 Get Remote Pools"); console.log("========================================"); console.log(string.concat("Chain: ", sourceChainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Remote Family:", " ", destChainFamilyStr)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "View remote pools")); console.log("========================================"); @@ -79,7 +92,10 @@ contract GetRemotePools is Script { bytes[] memory remotePools = PoolVersion._remotePools(tokenPoolAddress, version, remoteChainSelector); console.log(string.concat("Remote Pools: ", vm.toString(remotePools.length))); for (uint256 i = 0; i < remotePools.length; i++) { - if (remotePools[i].length == 32) { + if ( + destChainFamily == ChainHandlers.ChainFamily.EVM + && AddressEncoding._isAbiEncodedAddress(remotePools[i]) + ) { address poolAddr = abi.decode(remotePools[i], (address)); console.log(string.concat(" [", vm.toString(i), "] ", vm.toString(poolAddr))); } else { diff --git a/script/configure/remote-pools/GetRemoteToken.s.sol b/script/configure/remote-pools/GetRemoteToken.s.sol new file mode 100644 index 0000000..ec6a1fd --- /dev/null +++ b/script/configure/remote-pools/GetRemoteToken.s.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {HelperConfig} from "../../HelperConfig.s.sol"; +import {TokenPool} from "@chainlink/contracts-ccip/contracts/pools/TokenPool.sol"; +import {ChainHandlers} from "../../utils/ChainHandlers.s.sol"; +import {AddressEncoding} from "../../utils/AddressEncoding.s.sol"; + +/// @notice Reads and displays the remote token configured on a TokenPool for a given remote chain. +/// +/// Environment Variables (required): +/// DEST_CHAIN - The remote chain whose token address is being queried (e.g. MANTLE_SEPOLIA) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) +/// +/// Usage example: +/// DEST_CHAIN=MANTLE_SEPOLIA \ +/// forge script script/configure/remote-pools/GetRemoteToken.s.sol \ +/// --rpc-url $ETHEREUM_SEPOLIA_RPC_URL +contract GetRemoteToken is Script { + HelperConfig public helperConfig; + + function run() external { + // -- Required env vars ------------------------------------------------- + string memory destChainName = vm.envString("DEST_CHAIN"); + + // -- Resolve selector/family from destination config (EVM and non-EVM) + helperConfig = new HelperConfig(); + uint256 sourceChainId = block.chainid; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + string memory destChainFamilyStr = vm.envOr( + "DEST_CHAIN_FAMILY", bytes(destConfig.chainFamily).length > 0 ? destConfig.chainFamily : string("evm") + ); + ChainHandlers.ChainFamily destChainFamily = ChainHandlers._parseChainFamily(destChainFamilyStr); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + + string memory sourceChainName = helperConfig.getChainName(sourceChainId); + string memory remoteChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; + + // -- Resolve local token pool ----------------------------------------- + address tokenPoolAddress = helperConfig.getDeployedTokenPool(sourceChainId); + require( + tokenPoolAddress != address(0), + string.concat( + "Token pool not deployed. Set the ", + helperConfig.getNetworkConfig(sourceChainId).chainNameIdentifier, + "_TOKEN_POOL environment variable. Alternatively, use the inline alias TOKEN_POOL=0x..." + ) + ); + + // -- Header ------------------------------------------------------------ + console.log(""); + console.log("========================================"); + console.log(unicode"🪙 Get Remote Token"); + console.log("========================================"); + console.log(string.concat("Chain: ", sourceChainName)); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Remote Family:", " ", destChainFamilyStr)); + console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); + console.log(string.concat("Action: ", "View remote token")); + console.log("========================================"); + console.log(""); + + TokenPool tokenPool = TokenPool(tokenPoolAddress); + + bool isSupported = tokenPool.isSupportedChain(remoteChainSelector); + console.log(string.concat("Chain Supported: ", isSupported ? "Yes" : "No")); + + if (isSupported) { + bytes memory remoteToken = tokenPool.getRemoteToken(remoteChainSelector); + if (destChainFamily == ChainHandlers.ChainFamily.EVM && AddressEncoding._isAbiEncodedAddress(remoteToken)) { + address tokenAddr = abi.decode(remoteToken, (address)); + console.log(string.concat("Remote Token: ", vm.toString(tokenAddr))); + } else { + console.log(string.concat("Remote Token: (raw) ", vm.toString(remoteToken))); + } + } + + console.log(""); + console.log("========================================"); + console.log( + string.concat("Token Pool: ", helperConfig.getExplorerUrl(sourceChainId, "/address/", tokenPoolAddress)) + ); + console.log("========================================"); + console.log(""); + } +} diff --git a/script/configure/remote-pools/RemoveRemotePool.s.sol b/script/configure/remote-pools/RemoveRemotePool.s.sol index e12b733..9f218eb 100644 --- a/script/configure/remote-pools/RemoveRemotePool.s.sol +++ b/script/configure/remote-pools/RemoveRemotePool.s.sol @@ -8,6 +8,8 @@ import {PoolVersion} from "../../utils/PoolVersion.s.sol"; import {PoolVersions} from "../../../src/PoolVersions.sol"; import {CctActions} from "../../../src/actions/CctActions.sol"; import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; +import {ChainHandlers} from "../../utils/ChainHandlers.s.sol"; +import {AddressEncoding} from "../../utils/AddressEncoding.s.sol"; /// @notice Removes a remote pool address from a TokenPool for a given remote chain. /// @@ -17,7 +19,12 @@ import {EoaExecutor} from "../../../src/base/EoaExecutor.s.sol"; /// /// Environment Variables (required): /// DEST_CHAIN - The remote chain where the pool to remove is deployed (e.g. MANTLE_SEPOLIA) -/// REMOTE_POOL_ADDRESS - The address of the remote pool to remove +/// REMOTE_POOL_ADDRESS - The address of the remote pool to remove (EVM: 0x address; +/// SVM: base58; Aptos: 0x hex) +/// +/// Environment Variables (optional): +/// DEST_CHAIN_FAMILY - Override destination family (default: from DEST_CHAIN config) +/// DEST_CHAIN_SELECTOR - Override destination selector (default: from DEST_CHAIN config) /// /// Usage example: /// DEST_CHAIN=MANTLE_SEPOLIA \ @@ -29,13 +36,29 @@ contract RemoveRemotePool is EoaExecutor { function run() external { // ── Required env vars ────────────────────────────────────────────── string memory destChainName = vm.envString("DEST_CHAIN"); - address remotePoolAddress = vm.envAddress("REMOTE_POOL_ADDRESS"); + string memory remotePoolAddressRaw = vm.envString("REMOTE_POOL_ADDRESS"); - // ── Resolve chain IDs and selectors ─────────────────────────────── + // ── Resolve selector/family from destination config (EVM and non-EVM) ───── helperConfig = new HelperConfig(); uint256 sourceChainId = block.chainid; - uint256 destChainId = helperConfig.parseChainName(destChainName); - uint64 remoteChainSelector = helperConfig.getNetworkConfig(destChainId).chainSelector; + HelperConfig.NetworkConfig memory destConfig = helperConfig.getDestChainConfig(destChainName); + string memory destChainFamilyStr = vm.envOr( + "DEST_CHAIN_FAMILY", bytes(destConfig.chainFamily).length > 0 ? destConfig.chainFamily : string("evm") + ); + ChainHandlers.ChainFamily destChainFamily = ChainHandlers._parseChainFamily(destChainFamilyStr); + uint64 remoteChainSelector = uint64(vm.envOr("DEST_CHAIN_SELECTOR", uint256(destConfig.chainSelector))); + require( + remoteChainSelector != 0, "Chain selector is not defined for destination chain. Set DEST_CHAIN_SELECTOR." + ); + string memory remoteChainDisplayName = + bytes(destConfig.chainName).length > 0 ? destConfig.chainName : destChainName; + + require( + ChainHandlers._validateChainAddress(remotePoolAddressRaw, destChainFamily), + string.concat("Invalid ", destChainFamilyStr, " REMOTE_POOL_ADDRESS: ", remotePoolAddressRaw) + ); + bytes memory remotePoolAddressEncoded = + ChainHandlers._prepareChainAddressData(remotePoolAddressRaw, destChainFamily); // ── Resolve pool address ─────────────────────────────────────────── address tokenPoolAddress = helperConfig.getDeployedTokenPool(sourceChainId); @@ -48,7 +71,15 @@ contract RemoveRemotePool is EoaExecutor { ) ); - _removeRemotePool(tokenPoolAddress, remotePoolAddress, remoteChainSelector, destChainName, destChainId); + _removeRemotePool( + tokenPoolAddress, + remotePoolAddressRaw, + remotePoolAddressEncoded, + remoteChainSelector, + remoteChainDisplayName, + destChainFamilyStr, + destChainFamily + ); } /// @dev Everything after input resolution, starting with the version fence. Split from run() @@ -57,10 +88,12 @@ contract RemoveRemotePool is EoaExecutor { /// test suites). function _removeRemotePool( address tokenPoolAddress, - address remotePoolAddress, + string memory remotePoolAddressRaw, + bytes memory remotePoolAddressEncoded, uint64 remoteChainSelector, - string memory destChainName, - uint256 destChainId + string memory remoteChainDisplayName, + string memory destChainFamilyStr, + ChainHandlers.ChainFamily destChainFamily ) internal { uint256 sourceChainId = block.chainid; string memory sourceChainName = helperConfig.getChainName(sourceChainId); @@ -81,12 +114,12 @@ contract RemoveRemotePool is EoaExecutor { require( tokenPool.isSupportedChain(remoteChainSelector), - string.concat("Remote chain not supported on this pool: ", destChainName) + string.concat("Remote chain not supported on this pool: ", remoteChainDisplayName) ); require( - tokenPool.isRemotePool(remoteChainSelector, abi.encode(remotePoolAddress)), - string.concat("Remote pool not configured for this chain: ", vm.toString(remotePoolAddress)) + tokenPool.isRemotePool(remoteChainSelector, remotePoolAddressEncoded), + string.concat("Remote pool not configured for this chain: ", remotePoolAddressRaw) ); // ── Header ───────────────────────────────────────────────────────── @@ -95,12 +128,13 @@ contract RemoveRemotePool is EoaExecutor { console.log(unicode"➖ Remove Remote Pool"); console.log("========================================"); console.log(string.concat("Chain: ", sourceChainName)); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Remote Family:", " ", destChainFamilyStr)); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); console.log(string.concat("Action: ", "Remove remote pool")); console.log("========================================"); console.log(""); - console.log(string.concat("Pool to Remove: ", vm.toString(remotePoolAddress))); + console.log(string.concat("Pool to Remove: ", remotePoolAddressRaw)); console.log(""); console.log(unicode"⚠️ WARNING: All inflight transactions from this pool will be rejected after removal."); console.log(" Ensure there are no inflight transactions before proceeding."); @@ -110,7 +144,10 @@ contract RemoveRemotePool is EoaExecutor { bytes[] memory currentPools = tokenPool.getRemotePools(remoteChainSelector); console.log(string.concat("Current Remote Pools: ", vm.toString(currentPools.length))); for (uint256 i = 0; i < currentPools.length; i++) { - if (currentPools[i].length == 32) { + if ( + destChainFamily == ChainHandlers.ChainFamily.EVM + && AddressEncoding._isAbiEncodedAddress(currentPools[i]) + ) { console.log( string.concat(" [", vm.toString(i), "] ", vm.toString(abi.decode(currentPools[i], (address)))) ); @@ -122,9 +159,7 @@ contract RemoveRemotePool is EoaExecutor { console.log(string.concat("[Step 1] Removing remote pool on ", sourceChainName)); - _executeCalls( - CctActions._removeRemotePool(tokenPoolAddress, remoteChainSelector, abi.encode(remotePoolAddress)) - ); + _executeCalls(CctActions._removeRemotePool(tokenPoolAddress, remoteChainSelector, remotePoolAddressEncoded)); console.log(unicode"✅ Remote pool removed successfully!"); console.log(""); @@ -132,8 +167,8 @@ contract RemoveRemotePool is EoaExecutor { console.log(string.concat(unicode"✅ Complete on ", sourceChainName, "!")); console.log("========================================"); console.log(string.concat("Token Pool: ", vm.toString(tokenPoolAddress))); - console.log(string.concat("Remote Chain: ", helperConfig.getChainName(destChainId))); - console.log(string.concat("Removed Pool: ", vm.toString(remotePoolAddress))); + console.log(string.concat("Remote Chain: ", remoteChainDisplayName)); + console.log(string.concat("Removed Pool: ", remotePoolAddressRaw)); console.log( string.concat( "Token Pool: ", helperConfig.getExplorerUrl(sourceChainId, "/address/", tokenPoolAddress) @@ -142,4 +177,24 @@ contract RemoveRemotePool is EoaExecutor { console.log("========================================"); console.log(""); } + + /// @dev Backward-compat overload kept for test harnesses and older internal call sites. + /// Interprets `remotePoolAddress` as an EVM address and delegates to the family-aware path. + function _removeRemotePool( + address tokenPoolAddress, + address remotePoolAddress, + uint64 remoteChainSelector, + string memory, + uint256 destChainId + ) internal { + _removeRemotePool( + tokenPoolAddress, + vm.toString(remotePoolAddress), + abi.encode(remotePoolAddress), + remoteChainSelector, + helperConfig.getChainName(destChainId), + "evm", + ChainHandlers.ChainFamily.EVM + ); + } } diff --git a/script/setup/GetSupportedChains.s.sol b/script/setup/GetSupportedChains.s.sol index 146a6f0..a9e8106 100644 --- a/script/setup/GetSupportedChains.s.sol +++ b/script/setup/GetSupportedChains.s.sol @@ -7,6 +7,7 @@ import {ChainHandlers} from "../utils/ChainHandlers.s.sol"; import {TokenPool} from "@chainlink/contracts-ccip/contracts/pools/TokenPool.sol"; import {PoolVersion} from "../utils/PoolVersion.s.sol"; import {PoolVersions} from "../../src/PoolVersions.sol"; +import {AddressEncoding} from "../utils/AddressEncoding.s.sol"; /// @notice Reads and displays all remote chains supported by a TokenPool. /// @@ -71,21 +72,31 @@ contract GetSupportedChains is Script { for (uint256 i = 0; i < supportedChains.length; i++) { uint64 selector = supportedChains[i]; bytes[] memory remotePools = PoolVersion._remotePools(tokenPoolAddress, version, selector); - string memory remoteChainName = helperConfig.getChainNameBySelector(selector); + // Resolve the remote's family from its config by SELECTOR (not by display name): + // getChainNameBySelector returns the displayName ("Solana Devnet") but getDestChainConfig + // matches the identifier ("SOLANA_DEVNET"), so the round-trip misses for non-EVM chains + // and falls back to a zero/evm config - which would misdecode SVM pubkeys as EVM addresses + // and never reach the base58 branch. The selector-based lookup resolves the family directly. + HelperConfig.NetworkConfig memory remoteConfig = helperConfig.getDestChainConfigBySelector(selector); + string memory remoteChainName = bytes(remoteConfig.chainName).length > 0 + ? remoteConfig.chainName + : helperConfig.getChainNameBySelector(selector); + ChainHandlers.ChainFamily remoteFamily = ChainHandlers._parseChainFamily( + bytes(remoteConfig.chainFamily).length > 0 ? remoteConfig.chainFamily : string("evm") + ); console.log(string.concat(" [", vm.toString(i), "] ", remoteChainName, " (", vm.toString(selector), ")")); console.log(string.concat(" Remote Pools: ", vm.toString(remotePools.length))); for (uint256 j = 0; j < remotePools.length; j++) { bytes memory pool = remotePools[j]; - // ABI-encoded EVM address: 32 bytes with 12 leading zero bytes. - // Raw SVM pubkey: 32 bytes, no leading-zero padding. - if (pool.length == 32 && _isEvmEncoded(pool)) { + if (remoteFamily == ChainHandlers.ChainFamily.EVM && AddressEncoding._isAbiEncodedAddress(pool)) { console.log( string.concat(" [", vm.toString(j), "] ", vm.toString(abi.decode(pool, (address)))) ); - } else if (pool.length == 32) { + } else if (remoteFamily == ChainHandlers.ChainFamily.SVM) { // Raw SVM (Solana) public key - display as base58 console.log(string.concat(" [", vm.toString(j), "] ", ChainHandlers._encodeBase58(pool))); } else { + // Aptos (raw 32-byte hex) or any unrecognized shape - display as raw hex console.log(string.concat(" [", vm.toString(j), "] (raw) ", vm.toString(pool))); } } @@ -99,13 +110,4 @@ contract GetSupportedChains is Script { console.log("========================================"); console.log(""); } - - /// @dev Returns true if `data` looks like an ABI-encoded EVM address: - /// 32 bytes where the first 12 bytes are all zero. - function _isEvmEncoded(bytes memory data) private pure returns (bool) { - for (uint256 i = 0; i < 12; i++) { - if (data[i] != 0) return false; - } - return true; - } } diff --git a/script/utils/AddressEncoding.s.sol b/script/utils/AddressEncoding.s.sol new file mode 100644 index 0000000..e498c68 --- /dev/null +++ b/script/utils/AddressEncoding.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @notice Helpers for interpreting encoded address bytes in read-only scripts. +library AddressEncoding { + /// @dev True when `data` is a canonical ABI-encoded EVM address: a 32-byte word + /// with the high 12 bytes zero. + function _isAbiEncodedAddress(bytes memory data) internal pure returns (bool) { + if (data.length != 32) return false; + for (uint256 i = 0; i < 12; i++) { + if (data[i] != 0) return false; + } + return true; + } +} diff --git a/test/config/DynamicChainDiscovery.t.sol b/test/config/DynamicChainDiscovery.t.sol index 4251c0d..30b3434 100644 --- a/test/config/DynamicChainDiscovery.t.sol +++ b/test/config/DynamicChainDiscovery.t.sol @@ -104,6 +104,20 @@ contract DynamicChainDiscoveryTest is Test { ); assertEq(helperConfig.getChainNameBySelector(SCRATCH_SELECTOR), "Zz Scratch Dynamic", "getChainNameBySelector"); + // getDestChainConfigBySelector(selector) - the selector-keyed lookup GetSupportedChains uses + // to resolve a remote's family. Must resolve via the discovered-chain cache (the scratch chain + // has no hardcoded fast path), and must NOT depend on the displayName round-trip. + assertEq( + helperConfig.getDestChainConfigBySelector(SCRATCH_SELECTOR).chainSelector, + SCRATCH_SELECTOR, + "getDestChainConfigBySelector" + ); + assertEq( + helperConfig.getDestChainConfigBySelector(SCRATCH_SELECTOR).chainFamily, + "evm", + "getDestChainConfigBySelector family" + ); + // getConfiguredChains() enumerates the new chain alongside the committed ones string[] memory chains = helperConfig.getConfiguredChains(); bool foundScratch = false; diff --git a/test/config/HelperConfigGoldenData.t.sol b/test/config/HelperConfigGoldenData.t.sol index 4509087..7a82db2 100644 --- a/test/config/HelperConfigGoldenData.t.sol +++ b/test/config/HelperConfigGoldenData.t.sol @@ -193,6 +193,28 @@ contract HelperConfigGoldenDataTest is Test { assertEq(helperConfig.getDestChainConfig(NEVER_ONBOARDED_CHAIN_NAME).chainSelector, 0); assertEq(helperConfig.getDestChainConfig(NEVER_ONBOARDED_CHAIN_NAME).chainFamily, ""); + // getDestChainConfigBySelector: selector -> config (the selector-keyed analog of + // getDestChainConfig). This is the path GetSupportedChains uses to resolve a remote's + // family, and it must return the SVM family for Solana - the displayName round-trip + // (getChainNameBySelector -> getDestChainConfig) misses because getDestChainConfig matches + // the identifier ("SOLANA_DEVNET"), not the displayName ("Solana Devnet"). + assertEq( + helperConfig.getDestChainConfigBySelector(16015286601757825753).chainFamily, "evm", "EVM selector -> evm" + ); + assertEq( + helperConfig.getDestChainConfigBySelector(16423721717087811551).chainFamily, "svm", "SVM selector -> svm" + ); + assertEq( + helperConfig.getDestChainConfigBySelector(16423721717087811551).chainName, + "Solana Devnet", + "SVM selector -> displayName" + ); + assertEq( + helperConfig.getDestChainConfigBySelector(1).chainFamily, + "", + "unknown selector -> zero config (evm fallback)" + ); + // getExplorerUrl composition assertEq( helperConfig.getExplorerUrl(11155111, "/address/", 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59),