Skip to content
This repository has been archived by the owner on Dec 3, 2024. It is now read-only.

Commit

Permalink
base token bridge source/destination contracts and interfaces
Browse files Browse the repository at this point in the history
add disclaimers to contracts
  • Loading branch information
Matthew Lam committed Feb 22, 2024
1 parent 925e99c commit c6403c1
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 2 deletions.
4 changes: 2 additions & 2 deletions contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
@openzeppelin/[email protected]/=lib/teleporter/contracts/lib/openzeppelin-contracts/
@avalabs/[email protected]/=lib/teleporter/contracts/lib/subnet-evm/
@openzeppelin/[email protected]/=lib/teleporter/contracts/lib/openzeppelin-contracts/contracts/
@avalabs/[email protected]/=lib/teleporter/contracts/lib/subnet-evm/contracts/
@teleporter/=lib/teleporter/contracts/src/Teleporter/
161 changes: 161 additions & 0 deletions contracts/src/TeleporterTokenDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TeleporterMessageInput, TeleporterFeeInfo} from "@teleporter/ITeleporterMessenger.sol";
import {TeleporterOwnerUpgradeable} from "@teleporter/upgrades/TeleporterOwnerUpgradeable.sol";
import {ITeleporterTokenBridge} from "./interfaces/ITeleporterTokenBridge.sol";
import {IWarpMessenger} from
"@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Abstract contract for a Teleporter token bridge that receives tokens from a {TeleporterTokenSource} in exchange for the tokens of this token bridge instance.
*/
abstract contract TeleporterTokenDestination is
ITeleporterTokenBridge,
TeleporterOwnerUpgradeable
{
// Warp precompile used for sending and receiving Warp messages.
address public constant WARP_PRECOMPILE_ADDRESS = 0x0200000000000000000000000000000000000005;

// The blockchain ID of the chain this contract is deployed on.
bytes32 public immutable blockchainID;

// The blockchain ID of the source chain this contract receives tokens from.
bytes32 public immutable sourceBlockchainID;
// The address of the source token bridge instance this contract receives tokens from.
address public immutable tokenSourceAddress;
// The ERC20 token this contract uses to pay for Teleporter fees.
IERC20 public immutable feeToken;

/**
* @dev Initializes this destination token bridge instance to receive
* tokens from the specified source blockchain and token bridge instance.
*/
constructor(
address teleporterRegistryAddress,
address teleporterManager,
bytes32 sourceBlockchainID_,
address tokenSourceAddress_,
address feeToken_
) TeleporterOwnerUpgradeable(teleporterRegistryAddress, teleporterManager) {
sourceBlockchainID = sourceBlockchainID_;
tokenSourceAddress = tokenSourceAddress_;
// TODO: figure out if NativeTokenDestination passes in token or not.
// NativeTokenDestination will pass in erc20 token it deposits native tokens to pay for fees.
// ERC20Destination will pass in the erc20 token it's bridging.
feeToken = IERC20(feeToken_);
blockchainID = IWarpMessenger(WARP_PRECOMPILE_ADDRESS).getBlockchainID();
}

/**
* @dev Sends tokens transferred to this contract to the destination token bridge instance.
*
* Burns the bridged amount, and uses Teleporter to send a cross chain message.
* TODO: Determine if this can be abstracted to a common function with {TeleporterTokenSource}
* Requirements:
*
* - `input.destinationBlockchainID` cannot be the same as the current blockchainID
* - `input.destinationBridgeAddress` cannot be the zero address
* - `input.recipient` cannot be the zero address
* - `amount` must be greater than 0
* - `amount` must be greater than `input.primaryFee`
*/
function _send(SendTokensInput calldata input, uint256 amount) internal virtual {
require(
input.destinationBlockchainID != blockchainID,
"TeleporterTokenDestination: cannot bridge to same chain"
);
require(
input.destinationBridgeAddress != address(0),
"TeleporterTokenDestination: zero destination bridge address"
);
require(input.recipient != address(0), "TeleporterTokenDestination: zero recipient address");
require(amount > 0, "TeleporterTokenDestination: zero send amount");
require(
amount > input.primaryFee,
"TeleporterTokenDestination: insufficient amount to cover fees"
);

// TODO: For NativeTokenDestination before this _send, we should exchange the fee amount
// in native tokens for the fee amount in erc20 tokens. For ERC20Destination, we simply
// safeTransferFrom the full amount.
amount -= input.primaryFee;
_burn(amount);

bytes32 messageID = _sendTeleporterMessage(
TeleporterMessageInput({
destinationBlockchainID: sourceBlockchainID,
destinationAddress: tokenSourceAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: address(feeToken),
amount: input.primaryFee
}),
// TODO: placeholder value
requiredGasLimit: 0,
allowedRelayerAddresses: input.allowedRelayerAddresses,
message: abi.encode(
SendTokensInput({
destinationBlockchainID: input.destinationBlockchainID,
destinationBridgeAddress: input.destinationBridgeAddress,
recipient: input.recipient,
primaryFee: input.secondaryFee,
secondaryFee: 0,
// TODO: Does multihop allowed relayer need to be separate parameter?
allowedRelayerAddresses: input.allowedRelayerAddresses
}),
amount
)
})
);

emit SendTokens(messageID, msg.sender, amount);
}

/**
* @dev See {ITeleporterUpgradeable-_receiveTeleporterMessage}
*
* Verifies the source token bridge instance, and withdraws the amount to the recipient address.
*/
function _receiveTeleporterMessage(
bytes32 sourceBlockchainID_,
address originSenderAddress,
bytes memory message
) internal virtual override {
require(
sourceBlockchainID_ == sourceBlockchainID,
"TeleporterTokenDestination: invalid source chain"
);
require(
originSenderAddress == tokenSourceAddress,
"TeleporterTokenDestination: invalid token source address"
);
(address recipient, uint256 amount) = abi.decode(message, (address, uint256));

_withdraw(recipient, amount);
}

/**
* @dev Burns tokens from the sender's balance.
* @param amount The amount of tokens to burn
*/
// solhint-disable-next-line no-empty-blocks
function _burn(uint256 amount) internal virtual {}

/**
* @dev Withdraws tokens to the recipient address.
* @param recipient The address to withdraw tokens to
* @param amount The amount of tokens to withdraw
*/
// solhint-disable-next-line no-empty-blocks
function _withdraw(address recipient, uint256 amount) internal virtual {}
}
138 changes: 138 additions & 0 deletions contracts/src/TeleporterTokenSource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {TeleporterMessageInput, TeleporterFeeInfo} from "@teleporter/ITeleporterMessenger.sol";
import {TeleporterOwnerUpgradeable} from "@teleporter/upgrades/TeleporterOwnerUpgradeable.sol";
import {ITeleporterTokenBridge} from "./interfaces/ITeleporterTokenBridge.sol";
import {IWarpMessenger} from
"@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Abstract contract for a Teleporter token bridge that sends tokens to {TeleporterTokenDestination} instances.
*
* This contract also handles multihop transfers, where tokens sent from a {TeleporterTokenDestination}
* instance are forwarded to another {TeleporterTokenDestination} instance.
*/
abstract contract TeleporterTokenSource is ITeleporterTokenBridge, TeleporterOwnerUpgradeable {
// Warp precompile used for sending and receiving Warp messages.
address public constant WARP_PRECOMPILE_ADDRESS = 0x0200000000000000000000000000000000000005;

// The blockchain ID of the chain this contract is deployed on.
bytes32 public immutable blockchainID;

// The ERC20 token this contract uses to pay for Teleporter fees.
IERC20 public immutable feeToken;

// Tracks the balances of tokens sent to other bridge instances.
// Bridges are not allowed to unwrap more than has been sent to them.
// (destinationBlockchainID, destinationBridgeAddress) -> balance
mapping(
bytes32 destinationBlockchainID
=> mapping(address destinationBridgeAddress => uint256 balance)
) public bridgedBalances;

/**
* @dev Initializes this source token bridge instance to send
* tokens to the specified destination chain and token bridge instance.
*/
constructor(
address teleporterRegistryAddress,
address teleporterManager,
address feeToken_
) TeleporterOwnerUpgradeable(teleporterRegistryAddress, teleporterManager) {
feeToken = IERC20(feeToken_);
blockchainID = IWarpMessenger(WARP_PRECOMPILE_ADDRESS).getBlockchainID();
}

/**
* @dev Sends tokens transferred to this contract to the destination token bridge instance.
*
* Increases the bridge balance sent to each destination token bridge instance,
* and uses Teleporter to send a cross chain message.
* TODO: Determine if this can be abstracted to a common function with {TeleporterTokenDestination}
* Requirements:
*
* - `input.destinationBlockchainID` cannot be the same as the current blockchainID
* - `input.destinationBridgeAddress` cannot be the zero address
* - `input.recipient` cannot be the zero address
* - `amount` must be greater than 0
* - `amount` must be greater than `input.primaryFee`
*/
function _send(SendTokensInput memory input, uint256 amount) internal virtual {
require(
input.destinationBlockchainID != blockchainID,
"TeleporterTokenSource: cannot bridge to same chain"
);
require(
input.destinationBridgeAddress != address(0),
"TeleporterTokenSource: zero destination bridge address"
);
require(input.recipient != address(0), "TeleporterTokenSource: zero recipient address");
require(amount > 0, "TeleporterTokenSource: zero send amount");
require(
amount > input.primaryFee, "TeleporterTokenSource: insufficient amount to cover fees"
);

// Subtract fee amount from amount and increase bridge balance
amount -= input.primaryFee;
bridgedBalances[input.destinationBlockchainID][input.destinationBridgeAddress] += amount;

// send message to destinationBridgeAddress
bytes32 messageID = _sendTeleporterMessage(
TeleporterMessageInput({
destinationBlockchainID: input.destinationBlockchainID,
destinationAddress: input.destinationBridgeAddress,
feeInfo: TeleporterFeeInfo({
feeTokenAddress: address(feeToken),
amount: input.primaryFee
}),
// TODO: Set requiredGasLimit
requiredGasLimit: 0,
allowedRelayerAddresses: input.allowedRelayerAddresses,
message: abi.encode(input.recipient, amount)
})
);

emit SendTokens(messageID, msg.sender, amount);
}

function _receiveTeleporterMessage(
bytes32 sourceBlockchainID,
address originSenderAddress,
bytes memory message
) internal virtual override {
(SendTokensInput memory input, uint256 amount) =
abi.decode(message, (SendTokensInput, uint256));

// Check that bridge instance returning has sufficient amount in balance
uint256 senderBalance = bridgedBalances[sourceBlockchainID][originSenderAddress];
require(senderBalance >= amount, "TeleporterTokenSource: insufficient balance to unwrap");

// Decrement the bridge balance by the unwrap amount
bridgedBalances[sourceBlockchainID][originSenderAddress] = senderBalance - amount;

// decrement totalAmount from bridge balance
if (input.destinationBlockchainID == blockchainID) {
require(
input.destinationBridgeAddress == address(this),
"TeleporterTokenSource: invalid bridge address"
);
_withdraw(input.recipient, amount);
}

_send(input, amount);
}

// solhint-disable-next-line no-empty-blocks
function _withdraw(address recipient, uint256 amount) internal virtual {}
}
25 changes: 25 additions & 0 deletions contracts/src/interfaces/IERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {ITeleporterTokenBridge} from "./ITeleporterTokenBridge.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Interface for a Teleporter token bridge that sends ERC20 tokens to another chain.
*/
interface IERC20Bridge is ITeleporterTokenBridge {
/**
* @dev Sends ERC20 tokens transferred to this contract to the destination token bridge instance.
* @param input specifies information for delivery of the tokens
* @param amount amount of tokens to send
*/
function send(SendTokensInput calldata input, uint256 amount) external;
}
24 changes: 24 additions & 0 deletions contracts/src/interfaces/INativeTokenBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {ITeleporterTokenBridge} from "./ITeleporterTokenBridge.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Interface for a Teleporter token bridge that sends native tokens to another chain.
*/
interface INativeTokenBridge is ITeleporterTokenBridge {
/**
* @dev Sends native tokens transferred to this contract to the destination token bridge instance.
* @param input specifies information for delivery of the tokens
*/
function send(SendTokensInput calldata input) external payable;
}
Loading

0 comments on commit c6403c1

Please sign in to comment.