Skip to content

Commit

Permalink
Add interaction of Teleporter and fallback function (#206)
Browse files Browse the repository at this point in the history
add interaction of Teleporter and fallback function
  • Loading branch information
minghinmatthewlam authored Dec 20, 2023
1 parent 83ee0af commit 68b6f3a
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 1 deletion.

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions contracts/src/Teleporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
- [Data Flow](#data-flow)
- [Properties](#properties)
- [Fees](#fees)
- [Message Receipts and Fee Redemption](#message-receipts-and-fee-redemption)
- [Required Interface](#required-interface)
- [Teleporter Contract Deployment](#teleporter-contract-deployment)
- [Message Delivery and Execution](#message-delivery-and-execution)


Expand All @@ -19,6 +21,8 @@ The `ITeleporterMessenger` interface provides two primary methods:
The `ITeleporterReceiver` interface provides a single method. All contracts that wish to receive Teleporter messages on the destination chain must implement this interface:
- `receiveTeleporterMessage`: called by the Teleporter contract on the destination chain to deliver a message to the destination contract.

> Note: If a contract does not implement `ITeleporterReceiver`, but instead implements [fallback](https://docs.soliditylang.org/en/latest/contracts.html#fallback-function), the fallback function will be called when Teleporter attempts to perform message execution. The message execution is marked as failed if the fallback function reverts, otherwise it is marked as successfully executed.
## Data Flow
<div align="center">
<img src="../../../resources/TeleporterDataFlowDiagram.png?raw=true"/>
Expand Down
2 changes: 2 additions & 0 deletions contracts/src/Teleporter/TeleporterMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {

// Re-encode the payload by ABI encoding a call to the {receiveTeleporterMessage} function
// defined by the {ITeleporterReceiver} interface.
// If the destination address does not implement {receiveTeleporterMessage}, but does implement
// a fallback function, then the fallback function will be called instead.
bytes memory payload = abi.encodeCall(
ITeleporterReceiver.receiveTeleporterMessage,
(originBlockchainID, message.senderAddress, message.message)
Expand Down
148 changes: 148 additions & 0 deletions contracts/src/Teleporter/tests/FallbackReceiveTests.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {
TeleporterMessengerTest,
TeleporterMessage,
WarpMessage,
TeleporterMessageReceipt
} from "./TeleporterMessengerTest.t.sol";

enum FallbackReceiveAction {
Fail,
Succeed
}

contract FallbackReceiveApp {
bytes32 public originChainID;
address public originSenderAddress;
uint256 public nonce;

constructor(bytes32 originChainID_, address originSenderAddress_) {
originChainID = originChainID_;
originSenderAddress = originSenderAddress_;
}

// solhint-disable-next-line no-complex-fallback, payable-fallback
fallback(bytes calldata data) external returns (bytes memory) {
bytes4 selector = bytes4(data[:4]);
// Check that the function selector was for receiveTeleporterMessage.
require(
selector == bytes4(keccak256("receiveTeleporterMessage(bytes32,address,bytes)")),
"FallbackReceiveApp: Invalid selector"
);

(bytes32 originChainID_, address originSenderAddress_, bytes memory message) =
abi.decode(data[4:], (bytes32, address, bytes));

require(originChainID == originChainID_, "FallbackReceiveApp: Invalid origin chain ID");
require(
originSenderAddress == originSenderAddress_,
"FallbackReceiveApp: Invalid origin sender address"
);

FallbackReceiveAction action = abi.decode(message, (FallbackReceiveAction));

if (action == FallbackReceiveAction.Fail) {
require(false, "FallbackReceiveApp: Fallback failed");
} else if (action == FallbackReceiveAction.Succeed) {
nonce++;
} else {
revert("FallbackReceiveApp: Invalid action");
}

return abi.encode(nonce);
}
}

contract FallbackReceiveTest is TeleporterMessengerTest {
FallbackReceiveApp public destinationContract;

function setUp() public virtual override {
TeleporterMessengerTest.setUp();
destinationContract = new FallbackReceiveApp(
DEFAULT_ORIGIN_CHAIN_ID,
address(this)
);
}

function testFallbackSuccess() public {
uint256 nonce = destinationContract.nonce();

// Construct the mock message to be received.
TeleporterMessage memory messageToReceive = TeleporterMessage({
messageID: 1,
senderAddress: address(this),
destinationBlockchainID: DEFAULT_DESTINATION_CHAIN_ID,
destinationAddress: address(destinationContract),
requiredGasLimit: DEFAULT_REQUIRED_GAS_LIMIT,
allowedRelayerAddresses: new address[](0),
receipts: new TeleporterMessageReceipt[](0),
message: abi.encode(FallbackReceiveAction.Succeed)
});
WarpMessage memory warpMessage =
_createDefaultWarpMessage(DEFAULT_ORIGIN_CHAIN_ID, abi.encode(messageToReceive));

// Mock the call to the warp precompile to get the message.
_setUpSuccessGetVerifiedWarpMessageMock(0, warpMessage);

// Receive the message and check that message execution was successful.
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit MessageExecuted(DEFAULT_ORIGIN_CHAIN_ID, messageToReceive.messageID);
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit ReceiveCrossChainMessage(
warpMessage.sourceChainID,
messageToReceive.messageID,
address(this),
DEFAULT_RELAYER_REWARD_ADDRESS,
messageToReceive
);
teleporterMessenger.receiveCrossChainMessage(0, DEFAULT_RELAYER_REWARD_ADDRESS);

// Check that the nonce was incremented.
assertEq(destinationContract.nonce(), nonce + 1);
}

function testFallbackFail() public {
uint256 nonce = destinationContract.nonce();

// Construct the mock message to be received.
TeleporterMessage memory messageToReceive = TeleporterMessage({
messageID: 1,
senderAddress: address(this),
destinationBlockchainID: DEFAULT_DESTINATION_CHAIN_ID,
destinationAddress: address(destinationContract),
requiredGasLimit: DEFAULT_REQUIRED_GAS_LIMIT,
allowedRelayerAddresses: new address[](0),
receipts: new TeleporterMessageReceipt[](0),
message: abi.encode(FallbackReceiveAction.Fail)
});
WarpMessage memory warpMessage =
_createDefaultWarpMessage(DEFAULT_ORIGIN_CHAIN_ID, abi.encode(messageToReceive));

// Mock the call to the warp precompile to get the message.
_setUpSuccessGetVerifiedWarpMessageMock(0, warpMessage);

// Receive the message and check that message execution was successful.
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit MessageExecutionFailed(
DEFAULT_ORIGIN_CHAIN_ID, messageToReceive.messageID, messageToReceive
);
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit ReceiveCrossChainMessage(
warpMessage.sourceChainID,
messageToReceive.messageID,
address(this),
DEFAULT_RELAYER_REWARD_ADDRESS,
messageToReceive
);
teleporterMessenger.receiveCrossChainMessage(0, DEFAULT_RELAYER_REWARD_ADDRESS);

// Check that the nonce was not changed.
assertEq(destinationContract.nonce(), nonce);
}
}

0 comments on commit 68b6f3a

Please sign in to comment.