Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into teleporter-registry
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Lam committed Oct 27, 2023
2 parents f4f27bb + 28def26 commit a2d0d44
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 120 deletions.
108 changes: 54 additions & 54 deletions abi-bindings/go/Teleporter/TeleporterMessenger/TeleporterMessenger.go

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions contracts/src/Teleporter/ITeleporterMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ interface ITeleporterMessenger {
* @dev Emitted when Teleporter message is being delivered on destination chain and address,
* but message execution fails. Failed messages can then be retried.
*/
event FailedMessageExecution(
event MessageExecutionFailed(
bytes32 indexed originChainID,
uint256 indexed messageID,
TeleporterMessage message
);

/**
* @dev Emitted when a Teleporter message that previously failed to be executed properly
* is retried and is successfully executed. The message is then no longer able to be retried again
* in the future since each message will be successfully executed at most once.
* @dev Emitted when a Teleporter message is successfully executed with the
* specified destination address and message call data. This can occur either when
* the message is initially received, or on a retry attempt.
*
* Each message received can be executed successfully at most once.
*/
event MessageExecutionRetried(
event MessageExecuted(
bytes32 indexed originChainID,
uint256 indexed messageID
);
Expand Down
3 changes: 3 additions & 0 deletions contracts/src/Teleporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ Fees can be paid on a per message basis by specifing the ERC20 asset and amount
## Required Interface
Teleporter messages are delivered by calling the `receiveTeleporterMessage` function defined by the `ITeleporterReceiver` interface. Contracts must implement this interface in order to be able to receive messages. The first two paramaters of `receiveTeleporterMessage` identify the original sender of the given message on the origin chain and are set by the `TeleporterMessenger`. The third parameter to `receiveTeleporterMessage`, is the raw message payload. Applications using Teleporter are responsible for defining the exact format of this payload in a way that can be decoded on the receiving end. For example, applications may encode and action enum value along with the target method parameters on the sending side, then decode this data and route to the target method within `receiveTeleporterMessage`. See `ERC20Bridge.sol` for an example of this approach.

## Teleporter Contract Deployment
The `TeleporterMessenger` contract must be deployed to the same contract address on every chain. This is acheived using Nick's keyless transaction method as described [here](../../../utils/contract-deployment/README.md). As a result, Warp messages sent from the resulting contract address are ensured to have the same payload format as defined by the contract itself.

## Message Delivery and Execution
Teleporter is able to ensure that messages are considered delivered even if their execution fails (i.e. reverts) by using `evm.Call()` with a pre-defined gas limit to execute the message payload. This gas limit is specified by each message in the call to `sendCrossChainMessage`. Relayers must provide at least enough gas for the sub-call in addition to the standard gas used by a call to `receiveCrossChainMessage`. In the event that a message exeuction runs out of gas or reverts for any other reason, the hash of the message payload is stored by the receiving Teleporter contract instance. This allows for the message execution to be retried in the future, with possibly a higher gas limit. Importantly, a message is still considered delivered on its destination chain even if its execution fails. This allows the relayer of the message to redeem their reward for deliverying the message, because they have no control on whether or not its execution will succeed or not so long as they provide sufficient gas to meet the specified `requiredGasLimit`.
32 changes: 18 additions & 14 deletions contracts/src/Teleporter/TeleporterMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,9 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
uint256 public constant REQUIRED_ORIGIN_CHAIN_ID_START_INDEX = 4;
uint256 public constant MINIMUM_REQUIRED_CALL_DATA_LENGTH = 68;

// The blockchain ID of the chain the contract is deployed on. Determined by warp messenger precompile.
bytes32 public immutable blockchainID;

/**
* @dev Sets the value of `blockchainID` to the value determined by the warp messenger precompile.
*/
constructor() {
blockchainID = WARP_MESSENGER.getBlockchainID();
}
// The blockchain ID of the chain the contract is deployed on. Initialized lazily when receiveCrossChainMessage() is called,
// if the value has not already been set.
bytes32 public blockchainID;

/**
* @dev See {ITeleporterMessenger-sendCrossChainMessage}
Expand Down Expand Up @@ -247,8 +241,8 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
uint32 messageIndex,
address relayerRewardAddress
) external receiverNonReentrant {
// The relayer reward address is not allowed to be the zero address because it is how we track
// whether or not a message has been delivered.
// The relayer reward address is not allowed to be the zero address because it is how the
// contract tracks whether or not a message has been delivered.
require(
relayerRewardAddress != address(0),
"TeleporterMessenger: zero relayer reward address"
Expand All @@ -269,6 +263,11 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
"TeleporterMessenger: invalid origin sender address"
);

// If the blockchain ID has yet to be initialized, do so now.
if (blockchainID == bytes32(0)) {
blockchainID = WARP_MESSENGER.getBlockchainID();
}

// Require that the message was intended for this blockchain and teleporter contract.
require(
warpMessage.destinationChainID == blockchainID,
Expand Down Expand Up @@ -357,6 +356,7 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
* @dev See {ITeleporterMessenger-retryMessageExecution}
*
* Reverts if the message execution fails again on specified message.
* Emits a {MessageExecuted} event if the retry is successful.
* Requirements:
*
* - `message` must have previously failed to execute, and matches the hash of the failed message.
Expand Down Expand Up @@ -388,7 +388,7 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {

// Clear the failed message hash from state prior to retrying its execution to redundantly prevent
// reentrance attacks (on top of the nonReentrant guard).
emit MessageExecutionRetried(originChainID, message.messageID);
emit MessageExecuted(originChainID, message.messageID);
delete receivedFailedMessageHashes[originChainID][message.messageID];

// Reattempt the message execution with all of the gas left available for execution of this transaction.
Expand Down Expand Up @@ -702,7 +702,8 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
* (including possibly storing a failed message in state). All execution specific errors (i.e. invalid call data, etc)
* that are not in the relayers control are caught and handled properly.
*
* Emits a {FailedMessageExecution} event if the call on destination address fails with formatted call data.
* Emits a {MessageExecuted} event if the call on destination address is successful.
* Emits a {MessageExecutionFailed} event if the call on destination address fails with formatted call data.
* Requirements:
*
* - There is enough gas left to cover `message.requiredGasLimit`.
Expand Down Expand Up @@ -750,7 +751,10 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
// provided enough gas to meet the required gas limit.
if (!success) {
_storeFailedMessageExecution(originChainID, message);
return;
}

emit MessageExecuted(originChainID, message.messageID);
}

/**
Expand All @@ -766,7 +770,7 @@ contract TeleporterMessenger is ITeleporterMessenger, ReentrancyGuards {
] = keccak256(abi.encode(message));

// Emit a failed execution event for anyone monitoring unsuccessful messages to retry.
emit FailedMessageExecution(originChainID, message.messageID, message);
emit MessageExecutionFailed(originChainID, message.messageID, message);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ contract HandleInitialMessageExecutionTest is TeleporterMessengerTest {
// Mock the call to the warp precompile to get the message.
_setUpSuccessGetVerifiedWarpMessageMock(0, warpMessage);

// Receive the message.
// 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,
Expand Down Expand Up @@ -217,7 +222,7 @@ contract HandleInitialMessageExecutionTest is TeleporterMessengerTest {
// is considered a failed message execution, but the message itself is
// still successfully delivered.
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit FailedMessageExecution(
emit MessageExecutionFailed(
DEFAULT_ORIGIN_CHAIN_ID,
messageToReceive.messageID,
messageToReceive
Expand Down Expand Up @@ -280,7 +285,7 @@ contract HandleInitialMessageExecutionTest is TeleporterMessengerTest {

// Receive the message.
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit FailedMessageExecution(
emit MessageExecutionFailed(
DEFAULT_ORIGIN_CHAIN_ID,
messageToReceive.messageID,
messageToReceive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ contract RetryMessageExecutionTest is TeleporterMessengerTest {
// The message should be successfully received, but its execution should fail.
vm.roll(12);
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit FailedMessageExecution(
emit MessageExecutionFailed(
DEFAULT_ORIGIN_CHAIN_ID,
messageToReceive.messageID,
messageToReceive
Expand Down Expand Up @@ -285,7 +285,7 @@ contract RetryMessageExecutionTest is TeleporterMessengerTest {
// Now retry the message execution in a block with an odd height, which should succeed.
vm.roll(13);
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit MessageExecutionRetried(originChainID, message.messageID);
emit MessageExecuted(originChainID, message.messageID);
teleporterMessenger.retryMessageExecution(originChainID, message);

// Check that the message had the proper affect on the destination contract.
Expand Down
16 changes: 7 additions & 9 deletions contracts/src/Teleporter/tests/TeleporterMessengerTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ contract TeleporterMessengerTest is Test {
TeleporterMessage message
);

event FailedMessageExecution(
event MessageExecutionFailed(
bytes32 indexed originChainID,
uint256 indexed messageID,
TeleporterMessage message
);

event MessageExecutionRetried(
event MessageExecuted(
bytes32 indexed originChainID,
uint256 indexed messageID
);
Expand All @@ -88,13 +88,11 @@ contract TeleporterMessengerTest is Test {
);

teleporterMessenger = new TeleporterMessenger();
_mockFeeAsset = new UnitTestMockERC20();
}

function testCannotDeployWithoutWarpPrecompile() public {
vm.clearMockedCalls();
vm.expectRevert();
teleporterMessenger = new TeleporterMessenger();
// Blockchain ID should be 0 before it is initialized.
assertEq(teleporterMessenger.blockchainID(), bytes32(0));

_mockFeeAsset = new UnitTestMockERC20();
}

function testEmptyReceiptQueue() public {
Expand Down Expand Up @@ -242,7 +240,7 @@ contract TeleporterMessengerTest is Test {

// Receive the message - which should fail execution.
vm.expectEmit(true, true, true, true, address(teleporterMessenger));
emit FailedMessageExecution(
emit MessageExecutionFailed(
DEFAULT_ORIGIN_CHAIN_ID,
messageToReceive.messageID,
messageToReceive
Expand Down
18 changes: 12 additions & 6 deletions docker/run_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,28 @@ if [ ! -e $dir_prefix/NETWORK_RUNNING ]; then
# Verify that the transaction status was successful for the deployments
status=$(cast publish --rpc-url $subnet_a_url $teleporter_deploy_tx | getJsonVal "['status']")
if [[ $status != "0x1" ]]; then
echo "Error deploying Teleporter Messenger transaction on subnet A."
echo "Error deploying Teleporter Messenger on subnet A."
exit 1
fi
echo "Deployed TeleporterMessenger to Subnet A"
echo "Deployed TeleporterMessenger to Subnet A."
status=$(cast publish --rpc-url $subnet_b_url $teleporter_deploy_tx | getJsonVal "['status']")
if [[ $status != "0x1" ]]; then
echo "Error deploying Teleporter Messenger transaction on subnet B."
echo "Error deploying Teleporter Messenger on subnet B."
exit 1
fi
echo "Deployed TeleporterMessenger to Subnet B"
echo "Deployed TeleporterMessenger to Subnet B."
status=$(cast publish --rpc-url $subnet_c_url $teleporter_deploy_tx | getJsonVal "['status']")
if [[ $status != "0x1" ]]; then
echo "Error deploying Teleporter Messenger transaction on subnet C."
echo "Error deploying Teleporter Messenger on subnet C."
exit 1
fi
echo "Deployed TeleporterMessenger to Subnet C"
echo "Deployed TeleporterMessenger to Subnet C."
status=$(cast publish --rpc-url $c_chain_url $teleporter_deploy_tx | getJsonVal "['status']")
if [[ $status != "0x1" ]]; then
echo "Error deploying Teleporter Messenger on C-chain."
exit 1
fi
echo "Deployed TeleporterMessenger to C-chain."

# Deploy TeleporterRegistry to each chain.
cd contracts
Expand Down
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
Expand Down
34 changes: 7 additions & 27 deletions tests/utils/network_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/ava-labs/subnet-evm/plugin/evm"
"github.com/ava-labs/subnet-evm/rpc"
"github.com/ava-labs/subnet-evm/tests/utils/runner"
gasUtils "github.com/ava-labs/teleporter/utils/gas-utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -216,35 +215,15 @@ func DeployTeleporterContracts(transactionBytes []byte, deployerAddress common.A
for _, subnetInfo := range subnetsInfo {
client := subnetInfo.ChainWSClient

nonce, err := client.NonceAt(ctx, fundedAddress, nil)
Expect(err).Should(BeNil())
gasTipCap, err := client.SuggestGasTipCap(context.Background())
Expect(err).Should(BeNil())
baseFee, err := client.EstimateBaseFee(context.Background())
Expect(err).Should(BeNil())
gasFeeCap := baseFee.Mul(baseFee, big.NewInt(gasUtils.BaseFeeFactor))
gasFeeCap.Add(gasFeeCap, big.NewInt(gasUtils.MaxPriorityFeePerGas))
// Fund the deployer address
{
value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth
txA := types.NewTx(&types.DynamicFeeTx{
ChainID: subnetInfo.ChainIDInt,
Nonce: nonce,
To: &deployerAddress,
Gas: DefaultTeleporterTransactionGas,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Value: value,
})
txSigner := types.LatestSignerForChainID(subnetInfo.ChainIDInt)
triggerTx, err := types.SignTx(txA, txSigner, fundedKey)
Expect(err).Should(BeNil())

SendTransactionAndWaitForAcceptance(ctx, subnetInfo.ChainWSClient, triggerTx)
fundAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth
fundDeployerTx := createNativeTransferTransaction(ctx, subnetInfo, fundedAddress, fundedKey, deployerAddress, fundAmount)
SendTransactionAndWaitForAcceptance(ctx, subnetInfo.ChainWSClient, fundDeployerTx)
}
log.Info("Finished funding Teleporter deployer")
log.Info("Finished funding Teleporter deployer", "blockchainID", subnetInfo.BlockchainID.Hex())

// Deploy Teleporter
// Deploy Teleporter contract
{
rpcClient, err := rpc.DialContext(ctx, HttpToRPCURI(subnetInfo.ChainNodeURIs[0], subnetInfo.BlockchainID.String()))
Expect(err).Should(BeNil())
Expand All @@ -263,8 +242,9 @@ func DeployTeleporterContracts(transactionBytes []byte, deployerAddress common.A
Expect(err).Should(BeNil())
Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode
}
log.Info("Finished deploying Teleporter contracts")
log.Info("Finished deploying Teleporter contract", "blockchainID", subnetInfo.BlockchainID.Hex())
}
log.Info("Deployed Teleporter contracts to all subnets")
}

func TearDownNetwork() {
Expand Down
24 changes: 24 additions & 0 deletions tests/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
)

var (
NativeTransferGas uint64 = 21_000
DefaultTeleporterTransactionGas uint64 = 200_000
DefaultTeleporterTransactionGasFeeCap = big.NewInt(225 * params.GWei)
DefaultTeleporterTransactionGasTipCap = big.NewInt(params.GWei)
Expand Down Expand Up @@ -332,3 +333,26 @@ func calculateTxParams(ctx context.Context, wsClient ethclient.Client, fundedAdd

return gasFeeCap, gasTipCap, nonce
}

func createNativeTransferTransaction(
ctx context.Context,
network SubnetTestInfo,
fromAddress common.Address,
fromKey *ecdsa.PrivateKey,
recipient common.Address,
amount *big.Int,
) *types.Transaction {
gasFeeCap, gasTipCap, nonce := calculateTxParams(ctx, network.ChainWSClient, fundedAddress)

tx := types.NewTx(&types.DynamicFeeTx{
ChainID: network.ChainIDInt,
Nonce: nonce,
To: &recipient,
Gas: NativeTransferGas,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Value: amount,
})

return signTransaction(tx, fundedKey, network.ChainIDInt)
}

0 comments on commit a2d0d44

Please sign in to comment.