From 3100f40c74abb515dabf27305844900cb122b703 Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Wed, 7 Jul 2021 13:02:41 +0100 Subject: [PATCH 1/6] Initial implementation of arbitrum bridge functions. --- contracts/IArbToken.sol | 18 ++++++++++++++ contracts/MiniMeToken.sol | 51 +++++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 contracts/IArbToken.sol diff --git a/contracts/IArbToken.sol b/contracts/IArbToken.sol new file mode 100644 index 0000000..4570cf8 --- /dev/null +++ b/contracts/IArbToken.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.4.24; + +contract IArbToken { + /** + * @notice should increase token supply by amount, and should (probably) only be callable by the L1 bridge. + */ + function bridgeMint(address account, uint256 amount) external; + + /** + * @notice should decrease token supply by amount, and should (probably) only be callable by the L1 bridge. + */ + function bridgeBurn(address account, uint256 amount) external; + + /** + * @return address of layer 1 token + */ + address public l1Address; +} diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index a805ab2..b39ad5c 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -23,6 +23,7 @@ pragma solidity ^0.4.24; /// @dev It is ERC20 compliant, but still needs to under go further testing. import "./ITokenController.sol"; +import "./IArbToken.sol"; contract Controlled { /// @notice The address of the controller is the only address that can call @@ -55,14 +56,15 @@ contract ApproveAndCallFallBack { /// @dev The actual token contract, the default controller is the msg.sender /// that deploys the contract, so usually this token will be deployed by a /// token controller contract, which Giveth will call a "Campaign" -contract MiniMeToken is Controlled { +contract MiniMeToken is Controlled, IArbToken { string public name; //The Token's name: e.g. DigixDAO Tokens uint8 public decimals; //Number of decimals of the smallest unit string public symbol; //An identifier: e.g. REP string public version = "MMT_0.1"; //An arbitrary versioning scheme - bytes32 public nameHash; //Name Hash to generate the domain separator + bytes32 public nameHash; //Name Hash to generate the domain separator + address public bridge; mapping(address => uint256) public nonces; // Track the nonces used by the permit function mapping(address => mapping(bytes32 => bool)) public authorizationState; // Help to track the states of transferWithAutorization @@ -70,7 +72,7 @@ contract MiniMeToken is Controlled { // The chainId is hardcoded since solidity ^0.4.24 does not support `chainid` so we cannot get it dynamically // xDAI = 0x64 (100) uint256 public constant CHAINID = 0x64; - // bytes32 public view PERMIT_TYPEHASH = + // bytes32 public view PERMIT_TYPEHASH = // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; // bytes32 public view TRANSFER_WITH_AUTHORIZATION_TYPEHASH = @@ -141,6 +143,7 @@ contract MiniMeToken is Controlled { /// @param _decimalUnits Number of decimals of the new token /// @param _tokenSymbol Token Symbol for the new token /// @param _transfersEnabled If true, tokens will be able to be transferred + /// @param _l1Address The layer1 token address that this layer2 token represents function MiniMeToken( MiniMeTokenFactory _tokenFactory, MiniMeToken _parentToken, @@ -148,7 +151,8 @@ contract MiniMeToken is Controlled { string _tokenName, uint8 _decimalUnits, string _tokenSymbol, - bool _transfersEnabled + bool _transfersEnabled, + address _l1Address ) public { tokenFactory = _tokenFactory; @@ -158,6 +162,7 @@ contract MiniMeToken is Controlled { parentToken = _parentToken; parentSnapShotBlock = _parentSnapShotBlock; transfersEnabled = _transfersEnabled; + l1Address = _l1Address; creationBlock = block.number; nameHash = keccak256(_tokenName); } @@ -488,7 +493,8 @@ contract MiniMeToken is Controlled { _cloneTokenName, _cloneDecimalUnits, _cloneTokenSymbol, - _transfersEnabled + _transfersEnabled, + l1Address ); cloneToken.changeController(msg.sender); @@ -544,6 +550,31 @@ contract MiniMeToken is Controlled { transfersEnabled = _transfersEnabled; } + +//////////////// +// Arbitrum bridge functions +//////////////// + + modifier onlyBridge { + require(msg.sender == bridge); + _; + } + + /// @notice Changes the bridge with ability to assign and lock tokens + /// @param _bridge The new controller of the contract + function changeBridge(address _bridge) onlyController public { + bridge = _bridge; + } + + function bridgeMint(address _to, uint256 _amount) external onlyBridge { + require(transfersEnabled); + require(doTransfer(address(this), _to, _amount)); + } + + function bridgeBurn(address _from, uint256 _amount) external onlyBridge { + require(transferFrom(_from, address(this), _amount)); + } + //////////////// // Internal helper functions to query and set a value in a snapshot array //////////////// @@ -636,6 +667,10 @@ contract MiniMeToken is Controlled { return; } + if (_token == address(this)) { + return; + } + MiniMeToken token = MiniMeToken(_token); uint balance = token.balanceOf(this); token.transfer(controller, balance); @@ -684,7 +719,8 @@ contract MiniMeTokenFactory { string _tokenName, uint8 _decimalUnits, string _tokenSymbol, - bool _transfersEnabled + bool _transfersEnabled, + address _l1Address ) public returns (MiniMeToken) { MiniMeToken newToken = new MiniMeToken( @@ -694,7 +730,8 @@ contract MiniMeTokenFactory { _tokenName, _decimalUnits, _tokenSymbol, - _transfersEnabled + _transfersEnabled, + _l1Address ); newToken.changeController(msg.sender); From 38e1738da827d895a7b40fbac05f54f7b8db1fe3 Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Wed, 7 Jul 2021 13:32:37 +0100 Subject: [PATCH 2/6] Fix burn operation --- contracts/MiniMeToken.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index b39ad5c..68a750b 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -572,7 +572,8 @@ contract MiniMeToken is Controlled, IArbToken { } function bridgeBurn(address _from, uint256 _amount) external onlyBridge { - require(transferFrom(_from, address(this), _amount)); + require(transfersEnabled); + require(doTransfer(_from, address(this), _amount)); } //////////////// From 5157d1099896545328e9b3728d29020b323636d1 Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Thu, 22 Jul 2021 16:00:40 +0100 Subject: [PATCH 3/6] Add doc string, add correct chain id. --- contracts/MiniMeToken.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index 68a750b..016c4db 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -70,8 +70,8 @@ contract MiniMeToken is Controlled, IArbToken { mapping(address => mapping(bytes32 => bool)) public authorizationState; // Help to track the states of transferWithAutorization // The chainId is hardcoded since solidity ^0.4.24 does not support `chainid` so we cannot get it dynamically - // xDAI = 0x64 (100) - uint256 public constant CHAINID = 0x64; + // xDAI = 0x64 (100), Arbitrum = 42161, Arbtest = 421611 + uint256 public constant CHAINID = 421611; // bytes32 public view PERMIT_TYPEHASH = // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; @@ -713,6 +713,7 @@ contract MiniMeTokenFactory { /// @param _decimalUnits Number of decimals of the new token /// @param _tokenSymbol Token Symbol for the new token /// @param _transfersEnabled If true, tokens will be able to be transferred + /// @param _l1Address The layer1 token address that this layer2 token represents /// @return The address of the new token contract function createCloneToken( MiniMeToken _parentToken, From 92d7d8e9e2c7aa0d18bf8db44682a44be1091e2b Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Thu, 22 Jul 2021 17:28:47 +0100 Subject: [PATCH 4/6] Accept bridge in constructor. Use external address (not self/this) to hold bridgeable tokens. --- contracts/MiniMeToken.sol | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index 016c4db..dedc3e9 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -84,6 +84,7 @@ contract MiniMeToken is Controlled, IArbToken { // bytes32 public view VERSION_HASH = // keccak256("1") bytes32 public constant VERSION_HASH = 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; + address public constant BRIDGED_TOKENS_RESERVE = 0x0000000000000000000000000000000000B71d9E; /// @dev `Checkpoint` is the structure that attaches a block number to a @@ -144,6 +145,7 @@ contract MiniMeToken is Controlled, IArbToken { /// @param _tokenSymbol Token Symbol for the new token /// @param _transfersEnabled If true, tokens will be able to be transferred /// @param _l1Address The layer1 token address that this layer2 token represents + /// @param _bridge The address of the bridge contract able to grant/lock bridged tokens function MiniMeToken( MiniMeTokenFactory _tokenFactory, MiniMeToken _parentToken, @@ -152,7 +154,8 @@ contract MiniMeToken is Controlled, IArbToken { uint8 _decimalUnits, string _tokenSymbol, bool _transfersEnabled, - address _l1Address + address _l1Address, + address _bridge ) public { tokenFactory = _tokenFactory; @@ -163,6 +166,7 @@ contract MiniMeToken is Controlled, IArbToken { parentSnapShotBlock = _parentSnapShotBlock; transfersEnabled = _transfersEnabled; l1Address = _l1Address; + bridge = _bridge; creationBlock = block.number; nameHash = keccak256(_tokenName); } @@ -494,7 +498,8 @@ contract MiniMeToken is Controlled, IArbToken { _cloneDecimalUnits, _cloneTokenSymbol, _transfersEnabled, - l1Address + l1Address, + bridge ); cloneToken.changeController(msg.sender); @@ -568,12 +573,12 @@ contract MiniMeToken is Controlled, IArbToken { function bridgeMint(address _to, uint256 _amount) external onlyBridge { require(transfersEnabled); - require(doTransfer(address(this), _to, _amount)); + require(doTransfer(BRIDGED_TOKENS_RESERVE, _to, _amount)); } function bridgeBurn(address _from, uint256 _amount) external onlyBridge { require(transfersEnabled); - require(doTransfer(_from, address(this), _amount)); + require(doTransfer(_from, BRIDGED_TOKENS_RESERVE, _amount)); } //////////////// @@ -714,6 +719,7 @@ contract MiniMeTokenFactory { /// @param _tokenSymbol Token Symbol for the new token /// @param _transfersEnabled If true, tokens will be able to be transferred /// @param _l1Address The layer1 token address that this layer2 token represents + /// @param _bridge The address of the bridge contract able to grant/lock bridged tokens /// @return The address of the new token contract function createCloneToken( MiniMeToken _parentToken, @@ -722,7 +728,8 @@ contract MiniMeTokenFactory { uint8 _decimalUnits, string _tokenSymbol, bool _transfersEnabled, - address _l1Address + address _l1Address, + address _bridge ) public returns (MiniMeToken) { MiniMeToken newToken = new MiniMeToken( @@ -733,7 +740,8 @@ contract MiniMeTokenFactory { _decimalUnits, _tokenSymbol, _transfersEnabled, - _l1Address + _l1Address, + _bridge ); newToken.changeController(msg.sender); From 73e5fbe1605bef8353bb3e94b8141c2ca6fe4da2 Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Tue, 17 Aug 2021 15:13:38 +0100 Subject: [PATCH 5/6] Add tests for bridging functionality --- contracts/MiniMeToken.sol | 10 ++-- package.json | 2 +- test/minime.js | 111 +++++++++++++++++++++++++++++++++++--- 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index dedc3e9..e7ba1a1 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -561,7 +561,7 @@ contract MiniMeToken is Controlled, IArbToken { //////////////// modifier onlyBridge { - require(msg.sender == bridge); + require(msg.sender == bridge, "ERROR: Not bridge"); _; } @@ -572,13 +572,13 @@ contract MiniMeToken is Controlled, IArbToken { } function bridgeMint(address _to, uint256 _amount) external onlyBridge { - require(transfersEnabled); - require(doTransfer(BRIDGED_TOKENS_RESERVE, _to, _amount)); + require(transfersEnabled, "ERROR: transfers disabled"); + require(doTransfer(BRIDGED_TOKENS_RESERVE, _to, _amount), "ERROR: transfer failed"); } function bridgeBurn(address _from, uint256 _amount) external onlyBridge { - require(transfersEnabled); - require(doTransfer(_from, BRIDGED_TOKENS_RESERVE, _amount)); + require(transfersEnabled, "ERROR: transfers disabled"); + require(doTransfer(_from, BRIDGED_TOKENS_RESERVE, _amount), "ERROR: transfer failed"); } //////////////// diff --git a/package.json b/package.json index f2814d3..06c62ca 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/minime", + "name": "@1hive/minime", "version": "1.0.0", "private": false, "author": "Jordi Baylina", diff --git a/test/minime.js b/test/minime.js index 3ffc7e1..2f31632 100644 --- a/test/minime.js +++ b/test/minime.js @@ -16,17 +16,21 @@ contract('MiniMeToken', accounts => { let factory = {} let token = {} let clone1 = {} + const l1TokenAddress = accounts[6] + const bridgeAddress = accounts[7] it('should deploy contracts', async () => { factory = await MiniMeTokenFactory.new() token = await MiniMeToken.new( - factory.address, - 0, - 0, - 'MiniMe Test Token', - 18, - 'MMT', - true) + factory.address, + 0, + 0, + 'MiniMe Test Token', + 18, + 'MMT', + true, + l1TokenAddress, + bridgeAddress) assert.ok(token) }) @@ -270,6 +274,7 @@ contract('MiniMeToken', accounts => { await assertRevert(token.permit(_owner, _spender, utils.toHex(firstValue), utils.toHex(deadline), utils.toHex(firstSig.v), utils.toHex(firstSig.r), utils.toHex(firstSig.s)), '_validateSignedData: INVALID_SIGNATURE') }) }) + context('ERC-3009', () => { let from, fromPrivKey @@ -452,4 +457,96 @@ contract('MiniMeToken', accounts => { token.transferWithAuthorization(from, to, utils.toHex(secondValue), utils.toHex(validAfter), utils.toHex(validBefore), utils.toHex(nonce), utils.toHex(secondSig.v), utils.toHex(secondSig.r), utils.toHex(secondSig.s))) }) }) + + context('bridge minting', () => { + beforeEach(async () => { + factory = await MiniMeTokenFactory.new() + token = await MiniMeToken.new( + factory.address, + 0, + 0, + 'MiniMe Test Token', + 18, + 'MMT', + true, + l1TokenAddress, + bridgeAddress) + }) + + context('changeBridge(address _bridge)', () => { + it('can change bridge address', async () => { + const newBridgeAddress = accounts[8] + assert.equal(await token.bridge(), bridgeAddress, "Incorrect current bridge address") + await token.changeBridge(newBridgeAddress) + assert.equal(await token.bridge(), newBridgeAddress, "Incorrect new bridge address") + }) + + it('reverts when not controller', async () => { + await assertRevert(token.changeBridge(accounts[1], {from: accounts[1]})) + }) + }) + + context('bridgeMint(address _to, uint256 _amount)', () => { + it('tranafers from reserve to address', async () => { + const receiver = accounts[1] + const sendAmount = 100 + const reserveAddress = await token.BRIDGED_TOKENS_RESERVE() + await token.generateTokens(reserveAddress, sendAmount) + assert.equal(await token.balanceOf(receiver), 0, "Incorrect initial balance") + + await token.bridgeMint(receiver, sendAmount, { from: bridgeAddress }) + + assert.equal(await token.balanceOf(receiver), sendAmount, "Incorrect final balance") + assert.equal(await token.balanceOf(reserveAddress), 0, "Incorrect reserve balance") + }) + + it('reverts when not called from bridge', async () => { + await assertRevert(token.bridgeMint(accounts[1], 100), "ERROR: Not bridge") + }) + + it('reverts when no tokens to transfer', async () => { + await assertRevert(token.bridgeMint(accounts[1], 100, { from: bridgeAddress }), "ERROR: transfer failed") + }) + + it('reverts when transfers are disabled', async () => { + const sendAmount = 100 + await token.generateTokens(await token.BRIDGED_TOKENS_RESERVE(), sendAmount) + await token.enableTransfers(false) + + await assertRevert(token.bridgeMint(accounts[1], sendAmount, { from: bridgeAddress }), "ERROR: transfers disabled") + }) + }) + + context('bridgeBurn(address _from, uint256 _amount)', () => { + it('transfers to reserve address', async () => { + const burner = accounts[1] + const burnAmount = 100 + const reserveAddress = await token.BRIDGED_TOKENS_RESERVE() + await token.generateTokens(burner, burnAmount) + assert.equal(await token.balanceOf(burner), burnAmount, "Incorrect initial balance") + + await token.bridgeBurn(burner, burnAmount, { from: bridgeAddress }) + + assert.equal(await token.balanceOf(burner), 0, "Incorrect final balance") + assert.equal(await token.balanceOf(reserveAddress), burnAmount, "Incorrect reserve balance") + }) + + it('reverts when not called from bridge', async () => { + await assertRevert(token.bridgeBurn(accounts[1], 100), "ERROR: Not bridge") + }) + + it('reverts when no tokens to transfer', async () => { + await assertRevert(token.bridgeBurn(accounts[1], 100, { from: bridgeAddress }), "ERROR: transfer failed") + }) + + it('reverts when transfers are disabled', async () => { + const burner = accounts[1] + const burnAmount = 100 + await token.generateTokens(burner, burnAmount) + await token.enableTransfers(false) + + await assertRevert(token.bridgeBurn(burner, burnAmount, { from: bridgeAddress }), "ERROR: transfers disabled") + }) + }) + }) }) From 72c4e8b587d5718a98b0b8fd6fee94b534d64032 Mon Sep 17 00:00:00 2001 From: William Griffiths Date: Tue, 21 Sep 2021 11:55:18 +0100 Subject: [PATCH 6/6] Update check in claim tokens function. --- contracts/MiniMeToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/MiniMeToken.sol b/contracts/MiniMeToken.sol index e7ba1a1..65f79a2 100644 --- a/contracts/MiniMeToken.sol +++ b/contracts/MiniMeToken.sol @@ -673,7 +673,7 @@ contract MiniMeToken is Controlled, IArbToken { return; } - if (_token == address(this)) { + if (_token == BRIDGED_TOKENS_RESERVE) { return; }