Skip to content

Commit

Permalink
Make router destination/source chain specific (#1242)
Browse files Browse the repository at this point in the history
## Motivation
We want the ability to test new lanes using custom, test routers.
Therefore, we need a one-to-many relationship b/t routers and lanes,
rather than a singleton.

## Solution
Make routers configurable per lane, rather than per contract
  • Loading branch information
RyanRHall authored and asoliman92 committed Aug 28, 2024
1 parent b1694e2 commit 26b77df
Show file tree
Hide file tree
Showing 15 changed files with 697 additions and 284 deletions.
333 changes: 168 additions & 165 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

23 changes: 14 additions & 9 deletions contracts/src/v0.8/ccip/offRamp/EVM2EVMMultiOffRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,27 +89,28 @@ contract EVM2EVMMultiOffRamp is ITypeAndVersion, MultiOCR3Base {

/// @notice Per-chain source config (defining a lane from a Source Chain -> Dest OffRamp)
struct SourceChainConfig {
bool isEnabled; // ──────────╮ Flag whether the source chain is enabled or not
IRouter router; // ──────────╮ Local router to use for messages coming from this source chain
bool isEnabled; // | Flag whether the source chain is enabled or not
uint64 minSeqNr; // ─────────╯ The min sequence number expected for future messages
bytes onRamp; // OnRamp address on the source chain
}

/// @notice SourceChainConfig update args scoped to one source chain
struct SourceChainConfigArgs {
uint64 sourceChainSelector; // ───╮ Source chain selector of the config to update
IRouter router; // ────────────────╮ Local router to use for messages coming from this source chain
uint64 sourceChainSelector; // | Source chain selector of the config to update
bool isEnabled; // ────────────────╯ Flag whether the source chain is enabled or not
bytes onRamp; // OnRamp address on the source chain
}

/// @notice Dynamic offRamp config
/// @dev since OffRampConfig is part of OffRampConfigChanged event, if changing it, we should update the ABI on Atlas
struct DynamicConfig {
address router; // ─────────────────────────────────╮ Router address
address priceRegistry; // ──────────────────────────╮ Price registry address on the local chain
uint32 permissionLessExecutionThresholdSeconds; // │ Waiting time before manual execution is enabled
uint32 maxTokenTransferGas; // │ Maximum amount of gas passed on to token `transfer` call
uint32 maxPoolReleaseOrMintGas; // ─────────────────╯ Maximum amount of gas passed on to token pool when calling releaseOrMint
address messageValidator; // Optional message validator to validate incoming messages (zero address = no validator)
address priceRegistry; // Price registry address on the local chain
}

/// @notice a sequenceNumber interval
Expand Down Expand Up @@ -534,9 +535,9 @@ contract EVM2EVMMultiOffRamp is ITypeAndVersion, MultiOCR3Base {
|| !message.receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)
) return;

(bool success, bytes memory returnData,) = IRouter(s_dynamicConfig.router).routeMessage(
any2EvmMessage, Internal.GAS_FOR_CALL_EXACT_CHECK, message.gasLimit, message.receiver
);
(bool success, bytes memory returnData,) = s_sourceChainConfigs[message.header.sourceChainSelector]
.router
.routeMessage(any2EvmMessage, Internal.GAS_FOR_CALL_EXACT_CHECK, message.gasLimit, message.receiver);
// If CCIP receiver execution is not successful, revert the call including token transfers
if (!success) revert ReceiverError(returnData);
}
Expand Down Expand Up @@ -729,6 +730,10 @@ contract EVM2EVMMultiOffRamp is ITypeAndVersion, MultiOCR3Base {
revert ZeroChainSelectorNotAllowed();
}

if (address(sourceConfigUpdate.router) == address(0)) {
revert ZeroAddressNotAllowed();
}

SourceChainConfig storage currentConfig = s_sourceChainConfigs[sourceChainSelector];
bytes memory currentOnRamp = currentConfig.onRamp;
bytes memory newOnRamp = sourceConfigUpdate.onRamp;
Expand All @@ -746,8 +751,8 @@ contract EVM2EVMMultiOffRamp is ITypeAndVersion, MultiOCR3Base {
revert InvalidStaticConfig(sourceChainSelector);
}

// The only dynamic config is the isEnabled flag
currentConfig.isEnabled = sourceConfigUpdate.isEnabled;
currentConfig.router = sourceConfigUpdate.router;
emit SourceChainConfigSet(sourceChainSelector, currentConfig);
}
}
Expand All @@ -761,7 +766,7 @@ contract EVM2EVMMultiOffRamp is ITypeAndVersion, MultiOCR3Base {
/// @notice Sets the dynamic config.
/// @param dynamicConfig The dynamic config.
function _setDynamicConfig(DynamicConfig memory dynamicConfig) internal {
if (dynamicConfig.priceRegistry == address(0) || dynamicConfig.router == address(0)) {
if (dynamicConfig.priceRegistry == address(0)) {
revert ZeroAddressNotAllowed();
}

Expand Down
86 changes: 73 additions & 13 deletions contracts/src/v0.8/ccip/onRamp/EVM2EVMMultiOnRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {INonceManager} from "../interfaces/INonceManager.sol";
import {IPoolV1} from "../interfaces/IPool.sol";
import {IPriceRegistry} from "../interfaces/IPriceRegistry.sol";
import {IRMN} from "../interfaces/IRMN.sol";
import {IRouter} from "../interfaces/IRouter.sol";
import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol";

import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";
Expand All @@ -33,8 +34,10 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
error InvalidConfig();
error CursedByRMN(uint64 sourceChainSelector);
error GetSupportedTokensFunctionalityRemovedCheckAdminRegistry();
error InvalidDestChainConfig(uint64 sourceChainSelector);

event ConfigSet(StaticConfig staticConfig, DynamicConfig dynamicConfig);
event DestChainConfigSet(uint64 indexed destChainSelector, DestChainConfig destChainConfig);
event FeePaid(address indexed feeToken, uint256 feeValueJuels);
event FeeTokenWithdrawn(address indexed feeAggregator, address indexed feeToken, uint256 amount);
/// RMN depends on this event, if changing, please notify the RMN maintainers.
Expand All @@ -53,12 +56,29 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
/// @dev Struct to contains the dynamic configuration
// solhint-disable-next-line gas-struct-packing
struct DynamicConfig {
address router; // Router address
address priceRegistry; // Price registry address
address messageValidator; // Optional message validator to validate outbound messages (zero address = no validator)
address feeAggregator; // Fee aggregator address
}

/// @dev Struct to hold the configs for a destination chain
struct DestChainConfig {
// The last used sequence number. This is zero in the case where no messages has been sent yet.
// 0 is not a valid sequence number for any real transaction.
uint64 sequenceNumber;
// This is the local router address that is allowed to send messages to the destination chain.
// This is NOT the receiving router address on the destination chain.
IRouter router;
}

/// @dev Same as DestChainConfig but with the destChainSelector so that an array of these
/// can be passed in the constructor and the applyDestChainConfigUpdates function
//solhint-disable gas-struct-packing
struct DestChainConfigArgs {
uint64 destChainSelector; // Destination chain selector
IRouter router; // Source router address
}

// STATIC CONFIG
string public constant override typeAndVersion = "EVM2EVMMultiOnRamp 1.6.0-dev";
/// @dev The chain ID of the source chain that this contract is deployed to
Expand All @@ -75,12 +95,14 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
/// @dev The config for the onRamp
DynamicConfig internal s_dynamicConfig;

/// @dev Last used sequence number per destination chain.
/// This is zero in the case where no messages have been sent yet.
/// 0 is not a valid sequence number for any real transaction.
mapping(uint64 destChainSelector => uint64 sequenceNumber) internal s_destChainSequenceNumbers;
/// @dev The destination chain specific configs
mapping(uint64 destChainSelector => DestChainConfig destChainConfig) internal s_destChainConfigs;

constructor(StaticConfig memory staticConfig, DynamicConfig memory dynamicConfig) {
constructor(
StaticConfig memory staticConfig,
DynamicConfig memory dynamicConfig,
DestChainConfigArgs[] memory destChainConfigArgs
) {
if (
staticConfig.chainSelector == 0 || staticConfig.rmnProxy == address(0) || staticConfig.nonceManager == address(0)
|| staticConfig.tokenAdminRegistry == address(0)
Expand All @@ -94,6 +116,7 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
i_tokenAdminRegistry = staticConfig.tokenAdminRegistry;

_setDynamicConfig(dynamicConfig);
_applyDestChainConfigUpdates(destChainConfigArgs);
}

// ================================================================
Expand All @@ -104,7 +127,7 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
/// @param destChainSelector The destination chain selector
/// @return the next sequence number to be used
function getExpectedNextSequenceNumber(uint64 destChainSelector) external view returns (uint64) {
return s_destChainSequenceNumbers[destChainSelector] + 1;
return s_destChainConfigs[destChainSelector].sequenceNumber + 1;
}

/// @inheritdoc IEVM2AnyOnRampClient
Expand All @@ -114,15 +137,20 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
uint256 feeTokenAmount,
address originalSender
) external returns (bytes32) {
DestChainConfig storage destChainConfig = s_destChainConfigs[destChainSelector];

// NOTE: assumes the message has already been validated through the getFee call
// Validate message sender is set and allowed. Not validated in `getFee` since it is not user-driven.
if (originalSender == address(0)) revert RouterMustSetOriginalSender();
// Router address may be zero intentionally to pause.
if (msg.sender != s_dynamicConfig.router) revert MustBeCalledByRouter();
if (msg.sender != address(destChainConfig.router)) revert MustBeCalledByRouter();

address messageValidator = s_dynamicConfig.messageValidator;
if (messageValidator != address(0)) {
IMessageInterceptor(messageValidator).onOutboundMessage(destChainSelector, message);
{
// scoped to reduce stack usage
address messageValidator = s_dynamicConfig.messageValidator;
if (messageValidator != address(0)) {
IMessageInterceptor(messageValidator).onOutboundMessage(destChainSelector, message);
}
}

// Convert message fee to juels and retrieve converted args
Expand All @@ -139,7 +167,7 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
sourceChainSelector: i_chainSelector,
destChainSelector: destChainSelector,
// We need the next available sequence number so we increment before we use the value
sequenceNumber: ++s_destChainSequenceNumbers[destChainSelector],
sequenceNumber: ++destChainConfig.sequenceNumber,
// Only bump nonce for messages that specify allowOutOfOrderExecution == false. Otherwise, we
// may block ordered message nonces, which is not what we want.
nonce: isOutOfOrderExecution
Expand Down Expand Up @@ -256,9 +284,15 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
_setDynamicConfig(dynamicConfig);
}

/// @notice Gets the source router for a destination chain
/// @param destChainSelector The destination chain selector
/// @return router the router for the provided destination chain
function getRouter(uint64 destChainSelector) external view returns (IRouter) {
return s_destChainConfigs[destChainSelector].router;
}

/// @notice Internal version of setDynamicConfig to allow for reuse in the constructor.
function _setDynamicConfig(DynamicConfig memory dynamicConfig) internal {
// We permit router to be set to zero as a way to pause the contract.
if (dynamicConfig.priceRegistry == address(0) || dynamicConfig.feeAggregator == address(0)) revert InvalidConfig();

s_dynamicConfig = dynamicConfig;
Expand All @@ -274,6 +308,32 @@ contract EVM2EVMMultiOnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, OwnerIsCre
);
}

/// @notice Updates the destination chain specific config.
/// @param destChainConfigArgs Array of source chain specific configs.
function applyDestChainConfigUpdates(DestChainConfigArgs[] memory destChainConfigArgs) external onlyOwner {
_applyDestChainConfigUpdates(destChainConfigArgs);
}

/// @notice Internal version of applyDestChainConfigUpdates.
function _applyDestChainConfigUpdates(DestChainConfigArgs[] memory destChainConfigArgs) internal {
for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[i];
uint64 destChainSelector = destChainConfigArgs[i].destChainSelector;

if (destChainSelector == 0) {
revert InvalidDestChainConfig(destChainSelector);
}

DestChainConfig memory newDestChainConfig = DestChainConfig({
sequenceNumber: s_destChainConfigs[destChainSelector].sequenceNumber,
router: destChainConfigArg.router
});
s_destChainConfigs[destChainSelector] = newDestChainConfig;

emit DestChainConfigSet(destChainSelector, newDestChainConfig);
}
}

// ================================================================
// │ Tokens and pools │
// ================================================================
Expand Down
5 changes: 4 additions & 1 deletion contracts/src/v0.8/ccip/test/NonceManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ contract NonceManager_OnRampUpgrade is EVM2EVMMultiOnRampSetup {
s_outboundNonceManager.applyPreviousRampsUpdates(previousRamps);

(s_onRamp, s_metadataHash) = _deployOnRamp(
SOURCE_CHAIN_SELECTOR, address(s_sourceRouter), address(s_outboundNonceManager), address(s_tokenAdminRegistry)
SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry)
);

vm.startPrank(address(s_sourceRouter));
Expand Down Expand Up @@ -370,16 +370,19 @@ contract NonceManager_OffRampUpgrade is EVM2EVMMultiOffRampSetup {
EVM2EVMMultiOffRamp.SourceChainConfigArgs[] memory sourceChainConfigs =
new EVM2EVMMultiOffRamp.SourceChainConfigArgs[](3);
sourceChainConfigs[0] = EVM2EVMMultiOffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR_1,
isEnabled: true,
onRamp: ON_RAMP_ADDRESS_1
});
sourceChainConfigs[1] = EVM2EVMMultiOffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR_2,
isEnabled: true,
onRamp: ON_RAMP_ADDRESS_2
});
sourceChainConfigs[2] = EVM2EVMMultiOffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR_3,
isEnabled: true,
onRamp: ON_RAMP_ADDRESS_3
Expand Down
6 changes: 4 additions & 2 deletions contracts/src/v0.8/ccip/test/e2e/MultiRampsEnd2End.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ contract MultiRampsE2E is EVM2EVMMultiOnRampSetup, EVM2EVMMultiOffRampSetup {
s_onRamp2,
s_metadataHash2
) = _deployOnRamp(
SOURCE_CHAIN_SELECTOR + 1, address(s_sourceRouter2), address(s_nonceManager2), address(s_tokenAdminRegistry2)
SOURCE_CHAIN_SELECTOR + 1, s_sourceRouter2, address(s_nonceManager2), address(s_tokenAdminRegistry2)
);

address[] memory authorizedCallers = new address[](1);
Expand All @@ -81,18 +81,20 @@ contract MultiRampsE2E is EVM2EVMMultiOnRampSetup, EVM2EVMMultiOffRampSetup {
s_sourceRouter2.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0));

// Deploy offramp
_deployOffRamp(s_destRouter, s_mockRMN, s_inboundNonceManager);
_deployOffRamp(s_mockRMN, s_inboundNonceManager);

// Enable source chains on offramp
EVM2EVMMultiOffRamp.SourceChainConfigArgs[] memory sourceChainConfigs =
new EVM2EVMMultiOffRamp.SourceChainConfigArgs[](2);
sourceChainConfigs[0] = EVM2EVMMultiOffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR,
isEnabled: true,
// Must match OnRamp address
onRamp: abi.encode(address(s_onRamp))
});
sourceChainConfigs[1] = EVM2EVMMultiOffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR + 1,
isEnabled: true,
onRamp: abi.encode(address(s_onRamp2))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {IgnoreContractSize} from "./IgnoreContractSize.sol";
contract EVM2EVMMultiOnRampHelper is EVM2EVMMultiOnRamp, IgnoreContractSize {
constructor(
StaticConfig memory staticConfig,
DynamicConfig memory dynamicConfig
) EVM2EVMMultiOnRamp(staticConfig, dynamicConfig) {}
DynamicConfig memory dynamicConfig,
DestChainConfigArgs[] memory destChainConfigArgs
) EVM2EVMMultiOnRamp(staticConfig, dynamicConfig, destChainConfigArgs) {}
}
Loading

0 comments on commit 26b77df

Please sign in to comment.