Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimism & Avalanche bridged token contracts [v2] #41

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion contracts/v0.7/bridge/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# LINK Token Bridge v0.7

- `./token/LinkTokenChild.sol`: A mintable & burnable child LinkToken contract to be used on child networks.
- `./token/LinkTokenChild.sol`: A generalized mintable & burnable child LinkToken contract to be used on child networks.
- `./token/arbitrum/`: Documentation for the Arbitrum bridge.
- `./token/avalanche/`: Documentation and token implementation for the Avalanche bridge v2.
- `./token/optimism/`: Documentation and token implementation for the Optimism bridge v2.
43 changes: 33 additions & 10 deletions contracts/v0.7/bridge/token/LinkTokenChild.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,31 @@ import { ERC20Burnable } from "../../../../vendor/OpenZeppelin/openzeppelin-cont
import { SimpleWriteAccessController } from "../../../../vendor/smartcontractkit/chainlink/evm-contracts/src/v0.6/SimpleWriteAccessController.sol";
import { LinkToken } from "../../../v0.6/LinkToken.sol";

/// @dev Access controlled mintable & burnable LinkToken, for use on sidechains and L2 networks.
/**
* @dev Access controlled mintable & burnable LinkToken, for use on sidechains and L2 networks.
*
* NOTICE: Current implementation of LinkTokenChild contract requires some additional consideration:
* - Supporting more than one gateway (multiple bridges minting the same token) leaves room for accounting issues.
* If we have more than one gateway supported, an additional check needs to exist that limits withdrawals per
* gateway to an amount locked on L1, for the specific gateway. Otherwise one can accidentally "burn" tokens
* by withdrawing more than locked in L1 (tx will fail on L1). When there is a 1:1 relationship between the
* gateway and token, the token itself is an accounting mechanism. For a potential N:1 relationship, a more
* sophisticated type of accounting needs to exist.
* - Every bridge is unique in the amount of risk it bears, so tokens bridged by different bridges are not 1:1
* the same token, and shouldn't be forced as such.
* - Bridges often require an unique interface to be supported by the child network tokens
* (e.g. mint` vs. `deposit`, `burn` vs. `withdraw/unwrap`, etc.).
* - Bridges often assume that the child contract they are bridging to is the ERC20 token itself, not a gateway
* (intermediate contract) that could help us map from the specific bridge interface to our standard
* LinkTokenChild interface.
* - Chainlink often needs to launch on a new network before the native bridge interface is defined.
* - To support early (before the bridge is defined) Chainlink network launch, we could make an upgradeable
* LinkTokenChild contract which would enable us to slightly update the contract interface after the bridge
* gets defined, and once online transfer the ownership (bridge gateway role) to the new bridge.
*
* TODO: Make upgradeable and limit gateway support to only one (owner)!
*/
contract LinkTokenChild is ITypeAndVersion, IERC20Child, SimpleWriteAccessController, ERC20Burnable, LinkToken {
/**
* @dev Overrides parent contract so no tokens are minted on deployment.
* @inheritdoc LinkToken
*/
function _onCreate()
internal
override
{}

/**
* @notice versions:
*
Expand Down Expand Up @@ -86,6 +100,15 @@ contract LinkTokenChild is ITypeAndVersion, IERC20Child, SimpleWriteAccessContro
super.burnFrom(account, amount);
}

/**
* @dev Overrides parent contract so no tokens are minted on deployment.
* @inheritdoc LinkToken
*/
function _onCreate()
internal
override
{}

/// @inheritdoc LinkToken
function _transfer(
address sender,
Expand Down
8 changes: 8 additions & 0 deletions contracts/v0.7/bridge/token/arbitrum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# LINK Token on Arbitrum

Arbitrum included the ERC677 protocol support into their standard bridge token implementation, so **custom integration is not necessary**.

- Arbitrum ERC20/ERC677 token implementation: [StandardArbERC20.sol](https://github.com/OffchainLabs/arbitrum/blob/042bcafbccf09a23c149822436fb5ec0f4f6fe57/packages/arb-bridge-peripherals/contracts/tokenbridge/arbitrum/StandardArbERC20.sol)
- _Ethereum Rinkeby Arbitrum_ LINK token address: [0x615fBe6372676474d9e6933d310469c9b68e9726](https://rinkeby-explorer.arbitrum.io/address/0x615fBe6372676474d9e6933d310469c9b68e9726)
- _Ethereum Mainnet Arbitrum One_ LINK token address: [0xf97f4df75117a78c1A5a0DBb814Af92458539FB4](https://explorer.arbitrum.io/address/0xf97f4df75117a78c1A5a0DBb814Af92458539FB4)
- Arbitrum bridge UI: [https://bridge.arbitrum.io/](https://bridge.arbitrum.io/)
74 changes: 74 additions & 0 deletions contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.6.0 <0.8.0;

/* Interface Imports */
import { IERC20 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

/// @dev Interface for the bridged ERC20 token expected by the Avalanche standard bridge.
interface IERC20Avalanche is IERC20 {

function mint(
address to,
uint256 amount,
address fee_address,
uint256 fee_amount,
bytes32 origin_tx_id
)
external;

function chain_ids(
uint256 id
)
external
view
returns (bool);

function add_supported_chain_id(
uint256 chain_id
)
external;

/**
* @dev Destroys `amount` tokens from `msg.sender. This function is monitored by the Avalanche bridge.
* @notice Call this when withdrawing tokens from Avalanche (NOT the direct `burn/burnFrom` method!).
* @param amount Number of tokens to unwrap.
* @param chain_id Id of the chain/network where to withdraw tokens.
*/
function unwrap(
uint256 amount,
uint256 chain_id
)
external;

/**
* @dev Transfers bridge role from `msg.sender` to `new_bridge_role_address`.
* @param new_bridge_role_address Address of the new bridge operator.
*/
function migrate_bridge_role(
address new_bridge_role_address
)
external;

function add_swap_token(
address contract_address,
uint256 supply_increment
)
external;

function remove_swap_token(
address contract_address,
uint256 supply_decrement
)
external;

/**
* @dev Creates an L2 token connected to a specific L2 bridge gateway & L1 token
* @param token Address of the token to swap.
* @param amount Number of tokens to swap.
*/
function swap(
address token,
uint256 amount
)
external;
}
249 changes: 249 additions & 0 deletions contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.6.0 <0.8.0;

/* Interface Imports */
import { ITypeAndVersion } from "../../../../v0.6/ITypeAndVersion.sol";
import { IERC20Avalanche } from "./IERC20Avalanche.sol";

/* Library Imports */
import { Address } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/utils/Address.sol";
import { SafeMath } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";

/* Contract Imports */
import { Ownable } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/access/Ownable.sol";
import { ERC20 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { ERC20Burnable } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20Burnable.sol";
import { LinkToken } from "../../../../v0.6/LinkToken.sol";

/// @dev Access controlled mintable & burnable LinkToken, for use on Avalanche network.
contract LinkTokenAvalanche is ITypeAndVersion, IERC20Avalanche, ERC20Burnable, LinkToken, Ownable {
using SafeMath for uint256;

struct SwapToken {
address tokenContract;
uint256 supply;
}

mapping(address => SwapToken) s_swapTokens;
mapping(uint256 => bool) s_chainIds;

/**
* @notice versions:
*
* - LinkTokenAvalanche 0.0.1: initial release
*
* @inheritdoc ITypeAndVersion
*/
function typeAndVersion()
external
pure
override(ITypeAndVersion, LinkToken)
virtual
returns (string memory)
{
return "LinkTokenAvalanche 0.0.1";
}

/// @inheritdoc IERC20Avalanche
function chain_ids(
uint256 id
)
public
view
override
returns (bool)
{
return s_chainIds[id];
}

/// @inheritdoc IERC20Avalanche
function mint(
address to,
uint256 amount,
address fee_address,
uint256 fee_amount,
bytes32 /* origin_tx_id */
)
public
override
{
require(owner() == _msgSender(), "DOES_NOT_HAVE_BRIDGE_ROLE");
_mint(to, amount);
if (fee_amount > 0) {
_mint(fee_address, fee_amount);
}
}

/// @inheritdoc IERC20Avalanche
function add_supported_chain_id(
uint256 chain_id
)
public
override
{
require(owner() == _msgSender(), "DOES_NOT_HAVE_BRIDGE_ROLE");
s_chainIds[chain_id] = true;
}

/// @inheritdoc IERC20Avalanche
function unwrap(
uint256 amount,
uint256 chain_id
)
public
override
{
require(s_chainIds[chain_id] == true, "CHAIN_ID_NOT_SUPPORTED");
_burn(_msgSender(), amount);
}

/// @inheritdoc IERC20Avalanche
function migrate_bridge_role(
address new_bridge_role_address
)
public
override
{
require(owner() == _msgSender(), "DOES_NOT_HAVE_BRIDGE_ROLE");
transferOwnership(new_bridge_role_address);
}

/// @inheritdoc IERC20Avalanche
function add_swap_token(
address contract_address,
uint256 supply_increment
)
public
override
{
require(owner() == _msgSender(), "DOES_NOT_HAVE_BRIDGE_ROLE");
require(Address.isContract(contract_address), "ADDRESS_IS_NOT_CONTRACT");

// If the swap token is not already supported, add it with the total supply of supply_increment
// Otherwise, increment the current supply.
if (s_swapTokens[contract_address].tokenContract == address(0)) {
s_swapTokens[contract_address] = SwapToken({
tokenContract: contract_address,
supply: supply_increment
});
} else {
s_swapTokens[contract_address].supply =
s_swapTokens[contract_address].supply.add(supply_increment);
}
}

/// @inheritdoc IERC20Avalanche
function remove_swap_token(
address contract_address,
uint256 supply_decrement
)
public
override
{
require(owner() == _msgSender(), "DOES_NOT_HAVE_BRIDGE_ROLE");
require(Address.isContract(contract_address), "ADDRESS_IS_NOT_CONTRACT");
require(s_swapTokens[contract_address].tokenContract != address(0), "SWAP_TOKEN_IS_NOT_SUPPORTED");

// If the decrement is less than the current supply, decrement it from the current supply.
// Otherwise, if the decrement is greater than or equal to the current supply, delete the mapping value.
if (s_swapTokens[contract_address].supply > supply_decrement) {
s_swapTokens[contract_address].supply =
s_swapTokens[contract_address].supply.sub(supply_decrement);
} else {
delete s_swapTokens[contract_address];
}
}

/// @inheritdoc IERC20Avalanche
function swap(
address token,
uint256 amount
)
public
override
{
require(Address.isContract(token), "TOKEN_IS_NOT_CONTRACT");
require(s_swapTokens[token].tokenContract != address(0), "SWAP_TOKEN_IS_NOT_SUPPORTED");
require(amount <= s_swapTokens[token].supply, "SWAP_AMOUNT_MORE_THAN_ALLOWED_SUPPLY");

// Update the allowed swap amount.
s_swapTokens[token].supply = s_swapTokens[token].supply.sub(amount);

// Burn the old token.
ERC20Burnable swapToken = ERC20Burnable(s_swapTokens[token].tokenContract);
swapToken.burnFrom(_msgSender(), amount);

// Mint the new token.
_mint(_msgSender(), amount);
}

/**
* @notice WARNING: Will burn tokens, without withdrawing them to the origin chain!
* To withdraw tokens use the `unwrap` method, which is monitored by the bridge
*
* @inheritdoc ERC20Burnable
*/
function burn(
uint256 amount
)
public
override
virtual
{
super.burn(amount);
}

/**
* @notice WARNING: Will burn tokens, without withdrawing them to the origin chain!
* To withdraw tokens use the `unwrap` method, which is monitored by the bridge
*
* @inheritdoc ERC20Burnable
*/
function burnFrom(
address account,
uint256 amount
)
public
override
virtual
{
super.burnFrom(account, amount);
}

/**
* @dev Overrides parent contract so no tokens are minted on deployment.
* @inheritdoc LinkToken
*/
function _onCreate()
internal
override
{
s_chainIds[0] = true;
}

/// @inheritdoc LinkToken
function _transfer(
address sender,
address recipient,
uint256 amount
)
internal
override(ERC20, LinkToken)
virtual
{
super._transfer(sender, recipient, amount);
}

/// @inheritdoc LinkToken
function _approve(
address owner,
address spender,
uint256 amount
)
internal
override(ERC20, LinkToken)
virtual
{
super._approve(owner, spender, amount);
}
}
Loading