From ba42054aa35a737920800d5f2422c1d18b375b70 Mon Sep 17 00:00:00 2001 From: Stuart Kuentzel Date: Fri, 22 Oct 2021 10:16:34 +0900 Subject: [PATCH 1/2] uses arb test mint token as example for updated contracts --- contracts/Dev.sol | 132 +++++++++++++++++++++++------ contracts/TransferAndCallToken.sol | 52 ++++++++++++ interfaces/ICustomToken.sol | 37 ++++++++ interfaces/IL1CustomGateway.sol | 13 +++ interfaces/ITransferAndCall.sol | 25 ++++++ 5 files changed, 235 insertions(+), 24 deletions(-) create mode 100644 contracts/TransferAndCallToken.sol create mode 100644 interfaces/ICustomToken.sol create mode 100644 interfaces/IL1CustomGateway.sol create mode 100644 interfaces/ITransferAndCall.sol diff --git a/contracts/Dev.sol b/contracts/Dev.sol index f125b879..53d8090f 100644 --- a/contracts/Dev.sol +++ b/contracts/Dev.sol @@ -9,38 +9,96 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IInbox} from "interfaces/IInbox.sol"; import {IOutbox} from "interfaces/IOutbox.sol"; import {IBridge} from "interfaces/IBridge.sol"; +import {IL1CustomGateway} from "interfaces/IL1CustomGateway.sol"; +import {L1MintableToken, ICustomToken} from "interfaces/ICustomToken.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import {TransferAndCallToken} from "./TransferAndCallToken.sol"; +import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; -contract Dev is ERC20Upgradeable, ReentrancyGuardUpgradeable { +// https://github.com/OffchainLabs/arbitrum/blob/master/packages/arb-bridge-peripherals/contracts/tokenbridge/libraries/aeERC20.sol#L21 +// removes _setupDecimals since it is no longer used in OZ 0.4 + +contract aeERC20 is ERC20PermitUpgradeable, TransferAndCallToken, ReentrancyGuardUpgradeable { + using AddressUpgradeable for address; + + constructor() public initializer { + // this is expected to be used as the logic contract behind a proxy + // override the constructor if you don't wish to use the initialize method + } + + function _initialize( + string memory name_, + string memory symbol_ + ) internal initializer { + __ERC20Permit_init(name_); + __ERC20_init(name_, symbol_); + } +} + +contract ArbDEVTokenL1 is aeERC20, ICustomToken { using SafeERC20 for IERC20; - address public l2Token; - address public gateway; - address public inbox; + address public bridge; + bool private shouldRegisterGateway; address public devAddress; + address public gateway; + + // uint8 public constant TEST = uint8(0xa4b1); + + constructor(address _bridge) public { + bridge = _bridge; + aeERC20._initialize("Dev", "DEV"); + } + + function mint() external { + _mint(msg.sender, 50000000); + } - event EscrowMint(address indexed minter, uint256 amount); + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override(ERC20Upgradeable, ICustomToken) returns (bool) { + return ERC20Upgradeable.transferFrom(sender, recipient, amount); + } - function initialize(address _l2TokenAddr, address _gatewayAddr, address _inbox, address _devAddress) public initializer { - __ERC20_init("Dev", "DEV"); - l2Token = _l2TokenAddr; - gateway = _gatewayAddr; - inbox = _inbox; - devAddress = _devAddress; + function balanceOf(address account) + public + view + virtual + override(ERC20Upgradeable, ICustomToken) + returns (uint256) + { + return ERC20Upgradeable.balanceOf(account); } - function escrowMint(uint256 amount) external { - address msgSender = _l2Sender(); - require(msgSender == l2Token, "sender must be l2 token"); - _mint(gateway, amount); - emit EscrowMint(msgSender, amount); + /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2` + function isArbitrumEnabled() external view override returns (uint16) { + require(shouldRegisterGateway, "NOT_EXPECTED_CALL"); + // uint8 public constant TEST = uint8(0xa4b1); + return uint16(0xa4b1); + // return TEST; } - - function _l2Sender() private view returns (address) { - IBridge _bridge = IInbox(inbox).bridge(); - require(address(_bridge) != address(0), "bridge is zero address"); - IOutbox outbox = IOutbox(_bridge.activeOutbox()); - require(address(outbox) != address(0), "outbox is zero address"); - return outbox.l2ToL1Sender(); + + function registerTokenOnL2( + address l2CustomTokenAddress, + uint256 maxSubmissionCost, + uint256 maxGas, + uint256 gasPriceBid + // address creditBackAddress + ) public { + // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed + bool prev = shouldRegisterGateway; + shouldRegisterGateway = true; + + IL1CustomGateway(bridge).registerTokenToL2( + l2CustomTokenAddress, + maxGas, + gasPriceBid, + maxSubmissionCost + ); + + shouldRegisterGateway = prev; } /** @@ -73,4 +131,30 @@ contract Dev is ERC20Upgradeable, ReentrancyGuardUpgradeable { IERC20(devAddress).transfer(msg.sender, _amount); return true; } -} \ No newline at end of file +} + +contract MintableArbDEVL1 is L1MintableToken, ArbDEVTokenL1 { + + constructor(address _bridge) public ArbDEVTokenL1(_bridge) {} + + function bridgeMint(address account, uint256 amount) public override(L1MintableToken) { + _mint(account, amount); + } + + function balanceOf(address account) + public + view + override(ArbDEVTokenL1, ICustomToken) + returns (uint256 amount) + { + return super.balanceOf(account); + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public override(ArbDEVTokenL1, ICustomToken) returns (bool) { + return super.transferFrom(sender, recipient, amount); + } +} diff --git a/contracts/TransferAndCallToken.sol b/contracts/TransferAndCallToken.sol new file mode 100644 index 00000000..ded611ba --- /dev/null +++ b/contracts/TransferAndCallToken.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.9; + +// https://github.com/OffchainLabs/arbitrum/blob/master/packages/arb-bridge-peripherals/contracts/tokenbridge/libraries/TransferAndCallToken.sol + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "../interfaces/ITransferAndCall.sol"; + +// Implementation from https://github.com/smartcontractkit/LinkToken/blob/master/contracts/v0.6/TransferAndCallToken.sol +/** + * @notice based on Implementation from https://github.com/smartcontractkit/LinkToken/blob/master/contracts/v0.6/ERC677Token.sol + * The implementation doesn't return a bool on onTokenTransfer. This is similar to the proposed 677 standard, but still incompatible - thus we don't refer to it as such. + */ +abstract contract TransferAndCallToken is ERC20Upgradeable, ITransferAndCall { + /** + * @dev transfer token to a contract address with additional data if the recipient is a contact. + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + * @param _data The extra data to be passed to the receiving contract. + */ + function transferAndCall( + address _to, + uint256 _value, + bytes memory _data + ) public virtual override returns (bool success) { + super.transfer(_to, _value); + emit Transfer(msg.sender, _to, _value, _data); + if (isContract(_to)) { + contractFallback(_to, _value, _data); + } + return true; + } + + // PRIVATE + + function contractFallback( + address _to, + uint256 _value, + bytes memory _data + ) private { + ITransferAndCallReceiver receiver = ITransferAndCallReceiver(_to); + receiver.onTokenTransfer(msg.sender, _value, _data); + } + + function isContract(address _addr) private view returns (bool hasCode) { + uint256 length; + assembly { + length := extcodesize(_addr) + } + return length > 0; + } +} \ No newline at end of file diff --git a/interfaces/ICustomToken.sol b/interfaces/ICustomToken.sol new file mode 100644 index 00000000..ca84af03 --- /dev/null +++ b/interfaces/ICustomToken.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.0; + +// taken from https://github.com/OffchainLabs/arbitrum/blob/001bdeecdefbc4eda9a824ef7b39452b46faeb86/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/ICustomToken.sol + +interface ArbitrumEnabledToken { + /// @notice should return `0xa4b1` if token is enabled for arbitrum gateways + function isArbitrumEnabled() external view returns (uint16); +} + +/** + * @title Minimum expected interface for L1 custom token (see TestCustomTokenL1.sol for an example implementation) + */ +interface ICustomToken is ArbitrumEnabledToken { + /** + * @notice Should make an external call to EthERC20Bridge.registerCustomL2Token + */ + function registerTokenOnL2( + address l2CustomTokenAddress, + uint256 maxSubmissionCost, + uint256 maxGas, + uint256 gasPriceBid, + address creditBackAddress + ) external; + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + function balanceOf(address account) external view returns (uint256); +} + +interface L1MintableToken is ICustomToken { + function bridgeMint(address account, uint256 amount) external; +} \ No newline at end of file diff --git a/interfaces/IL1CustomGateway.sol b/interfaces/IL1CustomGateway.sol new file mode 100644 index 00000000..8dca1ea1 --- /dev/null +++ b/interfaces/IL1CustomGateway.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.0; + +/* + * @title Minimum expected interface for L1 custom gateway used https://github.com/OffchainLabs/arbitrum/blob/001bdeecdefbc4eda9a824ef7b39452b46faeb86/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol#L100 + */ +interface IL1CustomGateway { + function registerTokenToL2(address _l2Address, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable returns (uint256); +} \ No newline at end of file diff --git a/interfaces/ITransferAndCall.sol b/interfaces/ITransferAndCall.sol new file mode 100644 index 00000000..549b9fcc --- /dev/null +++ b/interfaces/ITransferAndCall.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +interface ITransferAndCall is IERC20Upgradeable { + function transferAndCall( + address to, + uint256 value, + bytes memory data + ) external returns (bool success); + + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); +} + +/** + * @notice note that implementation of ITransferAndCallReceiver is not expected to return a success bool + */ +interface ITransferAndCallReceiver { + function onTokenTransfer( + address _sender, + uint256 _value, + bytes memory _data + ) external; +} \ No newline at end of file From 50713472e30c344d1b6ac80c226c581320b8921a Mon Sep 17 00:00:00 2001 From: Stuart Kuentzel Date: Fri, 22 Oct 2021 10:46:00 +0900 Subject: [PATCH 2/2] passes addresses from mintable to dev --- contracts/Dev.sol | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/contracts/Dev.sol b/contracts/Dev.sol index 53d8090f..b06b9841 100644 --- a/contracts/Dev.sol +++ b/contracts/Dev.sol @@ -21,11 +21,6 @@ import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; contract aeERC20 is ERC20PermitUpgradeable, TransferAndCallToken, ReentrancyGuardUpgradeable { using AddressUpgradeable for address; - constructor() public initializer { - // this is expected to be used as the logic contract behind a proxy - // override the constructor if you don't wish to use the initialize method - } - function _initialize( string memory name_, string memory symbol_ @@ -45,9 +40,11 @@ contract ArbDEVTokenL1 is aeERC20, ICustomToken { // uint8 public constant TEST = uint8(0xa4b1); - constructor(address _bridge) public { + constructor(address _bridge, address _devAddress, address _gateway) public { bridge = _bridge; aeERC20._initialize("Dev", "DEV"); + devAddress = _devAddress; + gateway = _gateway; } function mint() external { @@ -135,7 +132,7 @@ contract ArbDEVTokenL1 is aeERC20, ICustomToken { contract MintableArbDEVL1 is L1MintableToken, ArbDEVTokenL1 { - constructor(address _bridge) public ArbDEVTokenL1(_bridge) {} + constructor(address _bridge, address _devAddress, address _gatewayAddress) public ArbDEVTokenL1(_bridge, _devAddress, _gatewayAddress) {} function bridgeMint(address account, uint256 amount) public override(L1MintableToken) { _mint(account, amount);