Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- Access controlled mintable & burnable LinkToken, for use on sidechains and L2 networks.
- Documentation and token implementation for the Avalanche bridge v2.
- Added versioning to v0.6 & v0.7 contracts

### Changed
Expand Down
2 changes: 2 additions & 0 deletions contracts/v0.7/bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ NOTICE: Current implementation of LinkTokenChild contract requires some addition
gets defined, and once online transfer the ownership (bridge gateway role) to the new bridge.

TODO: Potentially create an upgradeable `LinkTokenChild.sol` and limit gateway support to only one (owner)!

- `./token/avalanche/`: Documentation and token implementation for the Avalanche bridge v2.
73 changes: 73 additions & 0 deletions contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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;

/**
* @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);
}
}
13 changes: 13 additions & 0 deletions contracts/v0.7/bridge/token/avalanche/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# LINK Token on Avalanche

- `./token/IERC20Avalanche.sol`: Interface for the bridged ERC20 token expected by the Avalanche standard bridge.
- `./token/LinkTokenAvalanche.sol`: Access controlled mintable & burnable LinkToken, for use on Avalanche network.

`LinkTokenAvalanche.sol` is a slightly modified version of Avalanche's standard bridged ERC20 token and will be connected to the bridge operator once the bridge is online and operational. Modifications include:

- Contract versioning via `ITypeAndVersion` interface
- ERC677 support by extending the `LinkToken` contract
- Transfers & approves to the contract itself blocked (provided by `LinkToken` contract)
- Using OZ's `Ownable` contract to express the bridge operator role instead the original custom `Roles` contract

The public bridge contracts source code and addresses are still TBA by the Avalanche team.