Skip to content

Commit

Permalink
create message transformer on/off ramps that inherit from canonical 1…
Browse files Browse the repository at this point in the history
….6 on/off ramps
  • Loading branch information
jinhoonbang committed Jan 9, 2025
1 parent f84bc28 commit 5d5e85d
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 136 deletions.
26 changes: 26 additions & 0 deletions contracts/src/v0.8/ccip/interfaces/IMessageTransformer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Internal} from "../libraries/Internal.sol";

/// @notice Interface for plug-in message hook contracts that transform OffRamp & OnRamp messages.
/// The transformer functions are expected to revert on transform failures.
interface IMessageTransformer {
/// @notice Common error that can be thrown on transform failures and used by consumers
/// @param errorReason abi encoded revert reason
error MessageTransformError(bytes errorReason);

/// @notice Transforms the given OffRamp message. Reverts on transform failure
/// @param message to transform
/// @return transformed message
function transformInboundMessage(
Internal.Any2EVMRampMessage memory message
) external returns (Internal.Any2EVMRampMessage memory);

/// @notice Transforms the given OnRamp message. Reverts on transform failure
/// @param message to transform
/// @return transformed message
function transformOutboundMessage(
Internal.EVM2AnyRampMessage memory message
) external returns (Internal.EVM2AnyRampMessage memory);
}
39 changes: 39 additions & 0 deletions contracts/src/v0.8/ccip/offRamp/MessageTransformerOffRamp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {OffRamp} from "./OffRamp.sol";
import {IMessageTransformer} from "../interfaces/IMessageTransformer.sol";
import {Internal} from "../libraries/Internal.sol";

contract MessageTransformerOffRamp is OffRamp {

address internal s_messageTransformer;

constructor(
StaticConfig memory staticConfig,
DynamicConfig memory dynamicConfig,
SourceChainConfigArgs[] memory sourceChainConfigs,
address messageTransformerAddr
) OffRamp(staticConfig, dynamicConfig, sourceChainConfigs) {
if (address(messageTransformerAddr) == address(0)) {
revert ZeroAddressNotAllowed();
}
s_messageTransformer = messageTransformerAddr;
}

function getMessageTransformerAddress() external view returns (address) {
return s_messageTransformer;
}

function _beforeExecuteSingleMessage(
Internal.Any2EVMRampMessage memory message
) internal override returns (Internal.Any2EVMRampMessage memory transformedMessage) {
try IMessageTransformer(s_messageTransformer).transformInboundMessage(message) returns (
Internal.Any2EVMRampMessage memory m
) {
transformedMessage = m;
} catch (bytes memory err) {
revert IMessageTransformer.MessageTransformError(err);
}
}
}
20 changes: 9 additions & 11 deletions contracts/src/v0.8/ccip/offRamp/OffRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
import {IAny2EVMMessageReceiver} from "../interfaces/IAny2EVMMessageReceiver.sol";
import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
import {IMessageInterceptor} from "../interfaces/IMessageInterceptor.sol";
import {IMessageTransformer} from "../interfaces/IMessageTransformer.sol";
import {INonceManager} from "../interfaces/INonceManager.sol";
import {IPoolV1} from "../interfaces/IPool.sol";
import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
Expand Down Expand Up @@ -124,7 +123,6 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
uint32 permissionLessExecutionThresholdSeconds; // │ Waiting time before manual execution is enabled.
bool isRMNVerificationDisabled; // ────────────────╯ Flag whether the RMN verification is disabled or not.
address messageInterceptor; // Optional, validates incoming messages (zero address = no interceptor).
address messageTransformer; // Optional message transformer to transform incoming messages (zero address = no transformer)
}

/// @dev Report that is committed by the observing DON at the committing phase.
Expand Down Expand Up @@ -554,6 +552,14 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
return (Internal.MessageExecutionState.SUCCESS, "");
}

/// @notice hook for applying custom logic to the input message before executeSingleMessage()
/// @param message initial message
/// @return transformedMessage modified message
function _beforeExecuteSingleMessage(
Internal.Any2EVMRampMessage memory message
) internal virtual returns (Internal.Any2EVMRampMessage memory transformedMessage) {
}

/// @notice Executes a single message.
/// @param message The message that will be executed.
/// @param offchainTokenData Token transfer data to be passed to TokenPool.
Expand All @@ -568,15 +574,7 @@ contract OffRamp is ITypeAndVersion, MultiOCR3Base {
) external {
if (msg.sender != address(this)) revert CanOnlySelfCall();

if (s_dynamicConfig.messageTransformer != address(0)) {
try IMessageTransformer(s_dynamicConfig.messageTransformer).transformInboundMessage(message) returns (
Internal.Any2EVMRampMessage memory transformedMessage
) {
message = transformedMessage;
} catch (bytes memory err) {
revert IMessageTransformer.MessageTransformError(err);
}
}
message = _beforeExecuteSingleMessage(message);

Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0);
if (message.tokenAmounts.length > 0) {
Expand Down
41 changes: 41 additions & 0 deletions contracts/src/v0.8/ccip/onRamp/MessageTransformerOnRamp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {OnRamp} from "./OnRamp.sol";
import {IMessageTransformer} from "../interfaces/IMessageTransformer.sol";
import {Internal} from "../libraries/Internal.sol";

contract MessageTransformerOnRamp is OnRamp {

address internal s_messageTransformer;

error ZeroAddressNotAllowed();

constructor(
StaticConfig memory staticConfig,
DynamicConfig memory dynamicConfig,
DestChainConfigArgs[] memory destChainConfigs,
address messageTransformerAddr
) OnRamp(staticConfig, dynamicConfig, destChainConfigs) {
if (address(messageTransformerAddr) == address(0)) {
revert ZeroAddressNotAllowed();
}
s_messageTransformer = messageTransformerAddr;
}

function getMessageTransformerAddress() external view returns (address) {
return s_messageTransformer;
}

function _postProcessMessage(
Internal.EVM2AnyRampMessage memory message
) internal override returns (Internal.EVM2AnyRampMessage memory transformedMessage) {
try IMessageTransformer(s_messageTransformer).transformOutboundMessage(
message
) returns (Internal.EVM2AnyRampMessage memory m) {
transformedMessage = m;
} catch (bytes memory err) {
revert IMessageTransformer.MessageTransformError(err);
}
}
}
20 changes: 9 additions & 11 deletions contracts/src/v0.8/ccip/onRamp/OnRamp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
import {IEVM2AnyOnRampClient} from "../interfaces/IEVM2AnyOnRampClient.sol";
import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
import {IMessageInterceptor} from "../interfaces/IMessageInterceptor.sol";
import {IMessageTransformer} from "../interfaces/IMessageTransformer.sol";
import {INonceManager} from "../interfaces/INonceManager.sol";
import {IPoolV1} from "../interfaces/IPool.sol";
import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
Expand Down Expand Up @@ -72,7 +71,6 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender
address feeQuoter; // FeeQuoter address.
bool reentrancyGuardEntered; // Reentrancy protection.
address messageInterceptor; // Optional message interceptor to validate messages. Zero address = no interceptor.
address messageTransformer; // Optional message transformer to transform outbound messages (zero address = no transformer)
address feeAggregator; // Fee aggregator address.
address allowlistAdmin; // authorized admin to add or remove allowed senders.
}
Expand Down Expand Up @@ -241,15 +239,7 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender
newMessage.tokenAmounts[i].destExecData = destExecDataPerToken[i];
}

if (s_dynamicConfig.messageTransformer != address(0)) {
try IMessageTransformer(s_dynamicConfig.messageTransformer).transformOutboundMessage(
destChainSelector, newMessage
) returns (Internal.EVM2AnyRampMessage memory transformedMessage) {
newMessage = transformedMessage;
} catch (bytes memory err) {
revert IMessageTransformer.MessageTransformError(err);
}
}
newMessage = _postProcessMessage(newMessage);

// Hash only after all fields have been set.
newMessage.header.messageId = Internal._hash(
Expand All @@ -269,6 +259,14 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender
return newMessage.header.messageId;
}

/// @notice hook for applying custom logic to the message from router
/// @param message router message
/// @return transformedMessage modified message
function _postProcessMessage(
Internal.EVM2AnyRampMessage memory message
) internal virtual returns (Internal.EVM2AnyRampMessage memory transformedMessage) {
}

/// @notice Uses a pool to lock or burn a token.
/// @param tokenAndAmount Token address and amount to lock or burn.
/// @param destChainSelector Target destination chain selector of the message.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ contract MessageTransformerHelper is IMessageTransformer {

/// @inheritdoc IMessageTransformer
function transformOutboundMessage(
// solhint-disable-next-line no-unused-vars
uint64 _destChainSelector,
Internal.EVM2AnyRampMessage memory message
) public view returns (Internal.EVM2AnyRampMessage memory) {
if (s_shouldRevert) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {OffRamp} from "../../../offRamp/OffRamp.sol";
import {MessageTransformerOffRamp} from "../../../offRamp/MessageTransformerOffRamp.sol";
import {OffRampSetup} from "./OffRampSetup.t.sol";
import {Internal} from "../../../libraries/Internal.sol";
import {IMessageTransformer} from "../../../interfaces/IMessageTransformer.sol";
import {MessageTransformerHelper} from "../../helpers/MessageTransformerHelper.sol";
import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol";
import {Router} from "../../../Router.sol";

contract MessageTransformerOffRamp_executeSingleMessage is OffRampSetup {

MessageTransformerOffRamp internal s_messageTransformerOffRamp;


function setUp() public virtual override {
super.setUp();
s_messageTransformerOffRamp = new MessageTransformerOffRamp(
s_offRamp.getStaticConfig(),
s_offRamp.getDynamicConfig(),
new OffRamp.SourceChainConfigArgs[](0),
address(s_inboundMessageTransformer)
);

OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1);
sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({
router: s_destRouter,
sourceChainSelector: SOURCE_CHAIN_SELECTOR_1,
onRamp: ON_RAMP_ADDRESS_1,
isEnabled: true
});
s_messageTransformerOffRamp.applySourceChainConfigUpdates(sourceChainConfigs);

Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](0);
Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](2 * sourceChainConfigs.length);

for (uint256 i = 0; i < sourceChainConfigs.length; ++i) {
uint64 sourceChainSelector = sourceChainConfigs[i].sourceChainSelector;

offRampUpdates[2 * i] = Router.OffRamp({sourceChainSelector: sourceChainSelector, offRamp: address(s_messageTransformerOffRamp)});
offRampUpdates[2 * i + 1] = Router.OffRamp({
sourceChainSelector: sourceChainSelector,
offRamp: s_inboundNonceManager.getPreviousRamps(sourceChainSelector).prevOffRamp
});
}

s_destRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates);
}

function test_executeSingleMessage_WithMessageTransformer() public {
vm.stopPrank();
vm.startPrank(address(s_messageTransformerOffRamp));
Internal.Any2EVMRampMessage memory message = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1);
s_messageTransformerOffRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0));
}

function test_executeSingleMessage_WithMessageTransformer_RevertWhen_UnknownChain() public {
vm.stopPrank();
vm.startPrank(address(s_messageTransformerOffRamp));
Internal.Any2EVMRampMessage memory message = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1);
// Fail with any error (UnknownChain in this case) to check if OffRamp wraps the error with MessageTransformError during the revert
s_inboundMessageTransformer.setShouldRevert(true);
vm.expectRevert(
abi.encodeWithSelector(
IMessageTransformer.MessageTransformError.selector,
abi.encodeWithSelector(MessageTransformerHelper.UnknownChain.selector)
)
);
s_messageTransformerOffRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.24;

import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol";
import {IMessageTransformer} from "../../../interfaces/IMessageTransformer.sol";
import {IRouter} from "../../../interfaces/IRouter.sol";

import {Client} from "../../../libraries/Client.sol";
Expand All @@ -11,7 +10,6 @@ import {Pool} from "../../../libraries/Pool.sol";
import {OffRamp} from "../../../offRamp/OffRamp.sol";
import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol";
import {TokenPool} from "../../../pools/TokenPool.sol";
import {MessageTransformerHelper} from "../../helpers/MessageTransformerHelper.sol";
import {MaybeRevertMessageReceiverNo165} from "../../helpers/receivers/MaybeRevertMessageReceiverNo165.sol";
import {OffRampSetup} from "./OffRampSetup.t.sol";

Expand Down Expand Up @@ -81,36 +79,6 @@ contract OffRamp_executeSingleMessage is OffRampSetup {
s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0));
}

function test_executeSingleMessage_WithMessageTransformer() public {
vm.stopPrank();
vm.startPrank(OWNER);
_enableInboundMessageTransformer();
vm.startPrank(address(s_offRamp));
Internal.Any2EVMRampMessage memory message =
_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1);
s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0));
}

function test_executeSingleMessage_WithMessageTransformer_RevertWhen_UnknownChain() public {
vm.stopPrank();
vm.startPrank(OWNER);
_enableInboundMessageTransformer();
vm.startPrank(address(s_offRamp));
Internal.Any2EVMRampMessage memory message =
_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1);

// Fail with any error (UnknownChain in this case) to check if OffRamp wraps the error with MessageTransformError during the revert
s_inboundMessageTransformer.setShouldRevert(true);

vm.expectRevert(
abi.encodeWithSelector(
IMessageTransformer.MessageTransformError.selector,
abi.encodeWithSelector(MessageTransformerHelper.UnknownChain.selector)
)
);
s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0));
}

function test_executeSingleMessage_NonContract() public {
Internal.Any2EVMRampMessage memory message =
_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup {
feeQuoter: feeQuoter,
permissionLessExecutionThresholdSeconds: 60 * 60,
isRMNVerificationDisabled: false,
messageInterceptor: address(0),
messageTransformer: address(0)
messageInterceptor: address(0)
});
}

Expand Down Expand Up @@ -349,12 +348,6 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup {
s_offRamp.setDynamicConfig(dynamicConfig);
}

function _enableInboundMessageTransformer() internal {
OffRamp.DynamicConfig memory dynamicConfig = s_offRamp.getDynamicConfig();
dynamicConfig.messageTransformer = address(s_inboundMessageTransformer);
s_offRamp.setDynamicConfig(dynamicConfig);
}

function _redeployOffRampWithNoOCRConfigs() internal {
s_offRamp = new OffRampHelper(
OffRamp.StaticConfig({
Expand Down
Loading

0 comments on commit 5d5e85d

Please sign in to comment.