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

Arbitrum bridge functions (DO NOT MERGE) #3

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions contracts/IArbToken.sol
Original file line number Diff line number Diff line change
@@ -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;
}
65 changes: 56 additions & 9 deletions contracts/MiniMeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,22 +56,23 @@ 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

// 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 =
// 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;
// bytes32 public view TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
Expand All @@ -82,6 +84,7 @@ contract MiniMeToken is Controlled {
// bytes32 public view VERSION_HASH =
// keccak256("1")
bytes32 public constant VERSION_HASH = 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
address public constant BRIDGED_TOKENS_RESERVE = 0x0000000000000000000000000000000000B71d9E;
0xGabi marked this conversation as resolved.
Show resolved Hide resolved


/// @dev `Checkpoint` is the structure that attaches a block number to a
Expand Down Expand Up @@ -141,14 +144,18 @@ 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
/// @param _bridge The address of the bridge contract able to grant/lock bridged tokens
function MiniMeToken(
MiniMeTokenFactory _tokenFactory,
MiniMeToken _parentToken,
uint _parentSnapShotBlock,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol,
bool _transfersEnabled
bool _transfersEnabled,
address _l1Address,
address _bridge
) public
{
tokenFactory = _tokenFactory;
Expand All @@ -158,6 +165,8 @@ contract MiniMeToken is Controlled {
parentToken = _parentToken;
parentSnapShotBlock = _parentSnapShotBlock;
transfersEnabled = _transfersEnabled;
l1Address = _l1Address;
bridge = _bridge;
creationBlock = block.number;
nameHash = keccak256(_tokenName);
}
Expand Down Expand Up @@ -488,7 +497,9 @@ contract MiniMeToken is Controlled {
_cloneTokenName,
_cloneDecimalUnits,
_cloneTokenSymbol,
_transfersEnabled
_transfersEnabled,
l1Address,
bridge
);

cloneToken.changeController(msg.sender);
Expand Down Expand Up @@ -544,6 +555,32 @@ contract MiniMeToken is Controlled {
transfersEnabled = _transfersEnabled;
}


////////////////
// Arbitrum bridge functions
////////////////

modifier onlyBridge {
require(msg.sender == bridge, "ERROR: Not 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 {
0xGabi marked this conversation as resolved.
Show resolved Hide resolved
bridge = _bridge;
}

function bridgeMint(address _to, uint256 _amount) external onlyBridge {
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, "ERROR: transfers disabled");
require(doTransfer(_from, BRIDGED_TOKENS_RESERVE, _amount), "ERROR: transfer failed");
}

////////////////
// Internal helper functions to query and set a value in a snapshot array
////////////////
Expand Down Expand Up @@ -636,6 +673,10 @@ contract MiniMeToken is Controlled {
return;
}

if (_token == BRIDGED_TOKENS_RESERVE) {
return;
}

MiniMeToken token = MiniMeToken(_token);
uint balance = token.balanceOf(this);
token.transfer(controller, balance);
Expand Down Expand Up @@ -677,14 +718,18 @@ 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
/// @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,
uint _snapshotBlock,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol,
bool _transfersEnabled
bool _transfersEnabled,
address _l1Address,
address _bridge
) public returns (MiniMeToken)
{
MiniMeToken newToken = new MiniMeToken(
Expand All @@ -694,7 +739,9 @@ contract MiniMeTokenFactory {
_tokenName,
_decimalUnits,
_tokenSymbol,
_transfersEnabled
_transfersEnabled,
_l1Address,
_bridge
);

newToken.changeController(msg.sender);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@aragon/minime",
"name": "@1hive/minime",
"version": "1.0.0",
"private": false,
"author": "Jordi Baylina",
Expand Down
111 changes: 104 additions & 7 deletions test/minime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
})
})
})
})