From 196ddd167230c853e0d65f3324ccf7ffd775e515 Mon Sep 17 00:00:00 2001 From: Kristijan Rebernisak Date: Tue, 22 Jun 2021 18:07:55 +0200 Subject: [PATCH 1/4] Initial commit of Optimism & Avalanche bridged token contracts --- .../v0.7/bridge/token/LinkTokenChild.sol | 43 +++- .../v0.7/bridge/token/arbitrum/README.md | 1 + .../token/avalanche/IERC20Avalanche.sol | 56 +++++ .../token/avalanche/LinkTokenAvalanche.sol | 212 ++++++++++++++++++ .../v0.7/bridge/token/avalanche/README.md | 1 + .../bridge/token/optimism/IERC20Optimism.sol | 46 ++++ .../token/optimism/LinkTokenOptimism.sol | 109 +++++++++ .../v0.7/bridge/token/optimism/README.md | 1 + 8 files changed, 459 insertions(+), 10 deletions(-) create mode 100644 contracts/v0.7/bridge/token/arbitrum/README.md create mode 100644 contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol create mode 100644 contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol create mode 100644 contracts/v0.7/bridge/token/avalanche/README.md create mode 100644 contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol create mode 100644 contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol create mode 100644 contracts/v0.7/bridge/token/optimism/README.md diff --git a/contracts/v0.7/bridge/token/LinkTokenChild.sol b/contracts/v0.7/bridge/token/LinkTokenChild.sol index d6e75d4..3fb7a76 100644 --- a/contracts/v0.7/bridge/token/LinkTokenChild.sol +++ b/contracts/v0.7/bridge/token/LinkTokenChild.sol @@ -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: * @@ -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, diff --git a/contracts/v0.7/bridge/token/arbitrum/README.md b/contracts/v0.7/bridge/token/arbitrum/README.md new file mode 100644 index 0000000..234f356 --- /dev/null +++ b/contracts/v0.7/bridge/token/arbitrum/README.md @@ -0,0 +1 @@ +# LINK Token on Arbitrum diff --git a/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol new file mode 100644 index 0000000..bde12ac --- /dev/null +++ b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol @@ -0,0 +1,56 @@ +// 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"; + +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; + + function unwrap( + uint256 amount, + uint256 chain_id + ) + external; + + 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; + + function swap( + address token, + uint256 amount + ) + external; +} diff --git a/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol new file mode 100644 index 0000000..c33561d --- /dev/null +++ b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol @@ -0,0 +1,212 @@ +// 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); + } + + /** + * @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); + } +} diff --git a/contracts/v0.7/bridge/token/avalanche/README.md b/contracts/v0.7/bridge/token/avalanche/README.md new file mode 100644 index 0000000..0479d1d --- /dev/null +++ b/contracts/v0.7/bridge/token/avalanche/README.md @@ -0,0 +1 @@ +# LINK Token on Avalanche diff --git a/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol new file mode 100644 index 0000000..00cafd2 --- /dev/null +++ b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol @@ -0,0 +1,46 @@ +// 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"; +import { IERC165 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/introspection/IERC165.sol"; + +/// @dev Interface for L2 ERC20 tokens expected by the Optimism standard bridge L2 gateway. +interface IERC20Optimism is IERC20, IERC165 { + /// @dev Returns the address of an L1 token contract linked to this L2 token contract + function l1Token() + external + returns (address); + + /** + * @dev Creates `amount` tokens `to` account. + * @notice Called by L2 gateway to deposit tokens. + */ + function mint( + address to, + uint256 amount + ) + external; + + /** + * @dev Destroys `amount` tokens `from` account. + * @notice Called by L2 gateway to withdraw tokens. + */ + function burn( + address from, + uint256 amount + ) + external; + + /// @dev Emitted when `amount` tokens are deposited from L1 to L2. + event Mint( + address indexed account, + uint256 amount + ); + + /// @dev Emitted when `amount` tokens are withdrawn from L2 to L1. + event Burn( + address indexed account, + uint256 amount + ); +} diff --git a/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol b/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol new file mode 100644 index 0000000..e6e4605 --- /dev/null +++ b/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity >0.6.0 <0.8.0; + +/* Interface Imports */ +import { IERC165 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/introspection/IERC165.sol"; +import { ITypeAndVersion } from "../../../../v0.6/ITypeAndVersion.sol"; +import { IERC20Optimism } from "./IERC20Optimism.sol"; + +/* Contract Imports */ +import { ERC20 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import { LinkToken } from "../../../../v0.6/LinkToken.sol"; + +/// @dev Access controlled mintable & burnable LinkToken, for use on Optimism L2 network. +contract LinkTokenOptimism is ITypeAndVersion, IERC20Optimism, LinkToken { + /// @dev Returns the address of an L2 bridge contract that has access to mint & burn + address public immutable l2Bridge; + /// @inheritdoc IERC20Optimism + address public immutable override l1Token; + + /** + * @dev Creates an L2 token connected to a specific L2 bridge gateway & L1 token + * @param l2BridgeAddr Address of the corresponding L2 bridge gateway. + * @param l1TokenAddr Address of the corresponding L1 token. + */ + constructor( + address l2BridgeAddr, + address l1TokenAddr + ) { + l2Bridge = l2BridgeAddr; + l1Token = l1TokenAddr; + } + + /** + * @notice versions: + * + * - LinkTokenOptimism 0.0.1: initial release + * + * @inheritdoc ITypeAndVersion + */ + function typeAndVersion() + external + pure + override(ITypeAndVersion, LinkToken) + virtual + returns (string memory) + { + return "LinkTokenOptimism 0.0.1"; + } + + /// @dev Checks that message sender is the L2 bridge contract (locked access to mint & burn) + modifier onlyL2Bridge { + require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn"); + _; + } + + /** + * @dev Optimism standard bridge L2 gateway uses ERC165 to confirm the required interface + * @inheritdoc IERC165 + */ + function supportsInterface( + bytes4 interfaceId + ) + public + override + pure + returns (bool) + { + bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165 + bytes4 secondSupportedInterface = IERC20Optimism.l1Token.selector + ^ IERC20Optimism.mint.selector + ^ IERC20Optimism.burn.selector; + return interfaceId == firstSupportedInterface || interfaceId == secondSupportedInterface; + } + + /// @inheritdoc IERC20Optimism + function mint( + address recipient, + uint256 amount + ) + public + override + onlyL2Bridge() + { + _mint(recipient, amount); + emit Mint(recipient, amount); + } + + /// @inheritdoc IERC20Optimism + function burn( + address from, + uint256 amount + ) + public + override + onlyL2Bridge() + { + _burn(from, amount); + emit Burn(from, amount); + } + + /** + * @dev Overrides parent contract so no tokens are minted on deployment. + * @inheritdoc LinkToken + */ + function _onCreate() + internal + override + {} +} diff --git a/contracts/v0.7/bridge/token/optimism/README.md b/contracts/v0.7/bridge/token/optimism/README.md new file mode 100644 index 0000000..e313042 --- /dev/null +++ b/contracts/v0.7/bridge/token/optimism/README.md @@ -0,0 +1 @@ +# LINK Token on Optimism From 9eaed7a3e38d653ca3f51dd36e8f8d948d617960 Mon Sep 17 00:00:00 2001 From: Kristijan Rebernisak Date: Tue, 22 Jun 2021 19:47:34 +0200 Subject: [PATCH 2/4] Documentation update --- .../v0.7/bridge/token/arbitrum/README.md | 7 ++++ .../token/avalanche/IERC20Avalanche.sol | 16 +++++++++ .../token/avalanche/LinkTokenAvalanche.sol | 33 +++++++++++++++++++ .../bridge/token/optimism/IERC20Optimism.sol | 6 +++- 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/contracts/v0.7/bridge/token/arbitrum/README.md b/contracts/v0.7/bridge/token/arbitrum/README.md index 234f356..af37539 100644 --- a/contracts/v0.7/bridge/token/arbitrum/README.md +++ b/contracts/v0.7/bridge/token/arbitrum/README.md @@ -1 +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/) diff --git a/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol index bde12ac..620c298 100644 --- a/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol +++ b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol @@ -4,6 +4,7 @@ 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( @@ -25,12 +26,22 @@ interface IERC20Avalanche is IERC20 { ) 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 ) @@ -48,6 +59,11 @@ interface IERC20Avalanche is IERC20 { ) 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 diff --git a/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol index c33561d..3eefa7d 100644 --- a/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol +++ b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol @@ -173,6 +173,39 @@ contract LinkTokenAvalanche is ITypeAndVersion, IERC20Avalanche, ERC20Burnable, _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 diff --git a/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol index 00cafd2..9147295 100644 --- a/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol +++ b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol @@ -5,7 +5,7 @@ pragma solidity >0.6.0 <0.8.0; import { IERC20 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import { IERC165 } from "../../../../../vendor/OpenZeppelin/openzeppelin-contracts/contracts/introspection/IERC165.sol"; -/// @dev Interface for L2 ERC20 tokens expected by the Optimism standard bridge L2 gateway. +/// @dev Interface for the bridged ERC20 token expected by the Optimism standard bridge L2 gateway. interface IERC20Optimism is IERC20, IERC165 { /// @dev Returns the address of an L1 token contract linked to this L2 token contract function l1Token() @@ -15,6 +15,8 @@ interface IERC20Optimism is IERC20, IERC165 { /** * @dev Creates `amount` tokens `to` account. * @notice Called by L2 gateway to deposit tokens. + * @param to Address of the recipient. + * @param amount Number of tokens to mint. */ function mint( address to, @@ -25,6 +27,8 @@ interface IERC20Optimism is IERC20, IERC165 { /** * @dev Destroys `amount` tokens `from` account. * @notice Called by L2 gateway to withdraw tokens. + * @param from Address of the account holding the tokens to be burnt. + * @param amount Number of tokens to burn. */ function burn( address from, From 4ee9e7622d2b2835083745b198f81710a68b88fa Mon Sep 17 00:00:00 2001 From: Kristijan Rebernisak Date: Fri, 25 Jun 2021 14:47:45 +0200 Subject: [PATCH 3/4] Update IERC20Optimism.sol to use underscore args names (same as original) --- .../token/avalanche/IERC20Avalanche.sol | 4 ++- .../token/avalanche/LinkTokenAvalanche.sol | 10 ++++-- .../bridge/token/optimism/IERC20Optimism.sol | 32 +++++++++---------- .../token/optimism/LinkTokenOptimism.sol | 16 +++++----- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol index 620c298..c477419 100644 --- a/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol +++ b/contracts/v0.7/bridge/token/avalanche/IERC20Avalanche.sol @@ -16,7 +16,9 @@ interface IERC20Avalanche is IERC20 { ) external; - function chain_ids(uint256 id) + function chain_ids( + uint256 id + ) external view returns (bool); diff --git a/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol index 3eefa7d..252c844 100644 --- a/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol +++ b/contracts/v0.7/bridge/token/avalanche/LinkTokenAvalanche.sol @@ -45,7 +45,9 @@ contract LinkTokenAvalanche is ITypeAndVersion, IERC20Avalanche, ERC20Burnable, } /// @inheritdoc IERC20Avalanche - function chain_ids(uint256 id) + function chain_ids( + uint256 id + ) public view override @@ -125,7 +127,8 @@ contract LinkTokenAvalanche is ITypeAndVersion, IERC20Avalanche, ERC20Burnable, supply: supply_increment }); } else { - s_swapTokens[contract_address].supply = s_swapTokens[contract_address].supply.add(supply_increment); + s_swapTokens[contract_address].supply = + s_swapTokens[contract_address].supply.add(supply_increment); } } @@ -144,7 +147,8 @@ contract LinkTokenAvalanche is ITypeAndVersion, IERC20Avalanche, ERC20Burnable, // 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); + s_swapTokens[contract_address].supply = + s_swapTokens[contract_address].supply.sub(supply_decrement); } else { delete s_swapTokens[contract_address]; } diff --git a/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol index 9147295..2b15021 100644 --- a/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol +++ b/contracts/v0.7/bridge/token/optimism/IERC20Optimism.sol @@ -13,38 +13,38 @@ interface IERC20Optimism is IERC20, IERC165 { returns (address); /** - * @dev Creates `amount` tokens `to` account. + * @dev Creates `_amount` tokens `_to` account. * @notice Called by L2 gateway to deposit tokens. - * @param to Address of the recipient. - * @param amount Number of tokens to mint. + * @param _to Address of the recipient. + * @param _amount Number of tokens to mint. */ function mint( - address to, - uint256 amount + address _to, + uint256 _amount ) external; /** - * @dev Destroys `amount` tokens `from` account. + * @dev Destroys `_amount` tokens `_from` account. * @notice Called by L2 gateway to withdraw tokens. - * @param from Address of the account holding the tokens to be burnt. - * @param amount Number of tokens to burn. + * @param _from Address of the account holding the tokens to be burnt. + * @param _amount Number of tokens to burn. */ function burn( - address from, - uint256 amount + address _from, + uint256 _amount ) external; - /// @dev Emitted when `amount` tokens are deposited from L1 to L2. + /// @dev Emitted when `_amount` tokens are deposited from L1 to L2. event Mint( - address indexed account, - uint256 amount + address indexed _account, + uint256 _amount ); - /// @dev Emitted when `amount` tokens are withdrawn from L2 to L1. + /// @dev Emitted when `_amount` tokens are withdrawn from L2 to L1. event Burn( - address indexed account, - uint256 amount + address indexed _account, + uint256 _amount ); } diff --git a/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol b/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol index e6e4605..48f9df2 100644 --- a/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol +++ b/contracts/v0.7/bridge/token/optimism/LinkTokenOptimism.sol @@ -74,28 +74,28 @@ contract LinkTokenOptimism is ITypeAndVersion, IERC20Optimism, LinkToken { /// @inheritdoc IERC20Optimism function mint( - address recipient, - uint256 amount + address _to, + uint256 _amount ) public override onlyL2Bridge() { - _mint(recipient, amount); - emit Mint(recipient, amount); + _mint(_to, _amount); + emit Mint(_to, _amount); } /// @inheritdoc IERC20Optimism function burn( - address from, - uint256 amount + address _from, + uint256 _amount ) public override onlyL2Bridge() { - _burn(from, amount); - emit Burn(from, amount); + _burn(_from, _amount); + emit Burn(_from, _amount); } /** From 8a40b6fd2a37d759eac0ecee73d7fd5ee34d2db3 Mon Sep 17 00:00:00 2001 From: Kristijan Rebernisak Date: Fri, 25 Jun 2021 20:37:11 +0200 Subject: [PATCH 4/4] Improve token bridges docs --- contracts/v0.7/bridge/README.md | 5 ++++- contracts/v0.7/bridge/token/avalanche/README.md | 12 ++++++++++++ contracts/v0.7/bridge/token/optimism/README.md | 12 ++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/contracts/v0.7/bridge/README.md b/contracts/v0.7/bridge/README.md index e703e70..b64d150 100644 --- a/contracts/v0.7/bridge/README.md +++ b/contracts/v0.7/bridge/README.md @@ -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. diff --git a/contracts/v0.7/bridge/token/avalanche/README.md b/contracts/v0.7/bridge/token/avalanche/README.md index 0479d1d..3127b42 100644 --- a/contracts/v0.7/bridge/token/avalanche/README.md +++ b/contracts/v0.7/bridge/token/avalanche/README.md @@ -1 +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. diff --git a/contracts/v0.7/bridge/token/optimism/README.md b/contracts/v0.7/bridge/token/optimism/README.md index e313042..7513550 100644 --- a/contracts/v0.7/bridge/token/optimism/README.md +++ b/contracts/v0.7/bridge/token/optimism/README.md @@ -1 +1,13 @@ # LINK Token on Optimism + +- `./token/IERC20Optimism.sol`: Interface for the bridged ERC20 token expected by the Optimism standard bridge L2 gateway. +- `./token/LinkTokenOptimism.sol`: Access controlled mintable & burnable LinkToken, for use on Optimism L2 network. + +`LinkTokenOptimism.sol` is a slightly modified version of Optimism's [`L2StandardERC20.sol`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/optimistic-ethereum/libraries/standards/L2StandardERC20.sol) and will be connected to the [`OVM_L2StandardBridge.sol`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/tokens/OVM_L2StandardBridge.sol). 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) +- `l2Bridge` & `l1Token` were changed from storage vars to immutable vars, which provides some gas savings + +The [Optimism Gateway](https://gateway.optimism.io) bridge UI can be used to move the tokens between networks.