Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
upgradable owner address, owner/validator roles separation
Browse files Browse the repository at this point in the history
  • Loading branch information
clbrge committed Aug 24, 2018
1 parent 602fcdf commit 6cb17ae
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 68 deletions.
62 changes: 34 additions & 28 deletions contracts/BridgeRGEToken.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
Bridged RGE contract to be used in FOREIGN CHAIN (not Ethereum Mainnet)
Bridged ERC20 RGE contract to be used on a FOREIGN CHAIN (not Ethereum mainnet, i.e. "home")
See RougeBridge for the contract on Mainnet responsible to lock home RGE for every Bridged RGE
See RougeBridge for the contract on Ethereum mainnet (HOME CHAIN) responsible to lock home RGE for every Bridged RGE
*/

Expand All @@ -13,13 +13,11 @@ import "./EIP20.sol";
contract BridgeRGEToken is EIP20 {

/* ERC20 */
string public name;
string public symbol;
uint8 public decimals = 6;

/* RGEToken - keeping most of the interface similar to RGE home */
/* RGEToken - keeping the bridged RGE interface as similar as the home RGE */
address owner;
string public version = 'v1.0-0.2'; /* composition rge version + bridge version */
string public version = 'v1.0-0.4'; /* composition rge version + bridge version */
uint256 public totalSupply = 1000000000 * 10**uint(decimals);
uint256 public reserveY1 = 0;
uint256 public reserveY2 = 0;
Expand All @@ -30,20 +28,30 @@ contract BridgeRGEToken is EIP20 {
}

uint public network; /* the foreign RGE network ID */
address public homeAuthority; /* owner of the RougeBridge contract on mainnet */
address public bridge;
address public validator; /* this (foreign) validator */

constructor(uint _network, address _bridge, address _homeAuthority, string _name, string _symbol)
address public homeBridge;
address public homeValidator; /* validator in the RougeBridge contract on the home RGE network */

constructor(uint _network, address _validator, address _homeBridge, address _homeValidator, string _name, string _symbol)
EIP20 (totalSupply, _name, decimals, _symbol) public {
owner = msg.sender;
network = _network;
homeAuthority = _homeAuthority;
bridge = _bridge;
name = _name;
symbol = _symbol;
balances[address(0)] = totalSupply; /* RGE on address(0) means there are not on this foreign chain */
validator = _validator;
homeBridge = _homeBridge;
homeValidator = _homeValidator;
balances[address(0)] = totalSupply; /* RGE on address(0) means there are not circulating on this foreign chain */
}

function newOwner(address _account) onlyBy(owner) public {
owner = _account;
}

function setValidators(address _validator, address _homeValidator) onlyBy(owner) public {
validator = _validator;
homeValidator = _homeValidator;
}

bool public bridgeIsOpened = true;

function toggleBridge(bool _flag) onlyBy(owner) public {
Expand All @@ -58,21 +66,21 @@ contract BridgeRGEToken is EIP20 {
mapping (address => mapping (uint => bool)) public claimed;
mapping (address => mapping (uint => bool)) public surrendered;

/* the first hash can be read on the bridge main chain by the user */
/* the second hash is be sure that the RGE tokens are already locked + homeAuthority signature */
/* the lockHash can be read on the bridge main chain by the user */
/* the sealHash is be sure that the RGE tokens are already locked + homeValidator signature */

event RGEClaim(address indexed account, uint indexed _network, uint indexed depositBlock, uint256 value);

function claim(bytes32 _sealHash, uint256 _value, uint _depositBlock, uint _lockBlock,
uint8 vLock, bytes32 rLock, bytes32 sLock, uint8 vAuth, bytes32 rAuth, bytes32 sAuth)
BridgeOpen public returns (bool success) {
require(msg.sender != homeAuthority);
require(msg.sender != homeValidator);
require(balances[address(0)] >= _value);
require(!claimed[msg.sender][_depositBlock]); // check if the deposit has not been already claimed
bytes32 _lockHash = keccak256(abi.encodePacked(msg.sender, _value, network, bridge, _depositBlock));
require(ecrecover(prefixed(_lockHash), vLock, rLock, sLock) == owner);
bytes32 _lockHash = keccak256(abi.encodePacked(msg.sender, _value, network, homeBridge, _depositBlock));
require(ecrecover(prefixed(_lockHash), vLock, rLock, sLock) == validator);
require(_sealHash == keccak256(abi.encodePacked(_lockHash, vLock, rLock, sLock, _lockBlock)));
require(ecrecover(prefixed(_sealHash), vAuth, rAuth, sAuth) == homeAuthority);
require(ecrecover(prefixed(_sealHash), vAuth, rAuth, sAuth) == homeValidator);
claimed[msg.sender][_depositBlock] = true; // distribute the claim
balances[address(0)] -= _value;
balances[msg.sender] += _value;
Expand All @@ -84,11 +92,11 @@ contract BridgeRGEToken is EIP20 {
event Surrender(address indexed account, uint indexed _network, uint indexed depositBlock, uint256 value);

function surrender(uint256 _value, uint _depositBlock, uint8 vLock, bytes32 rLock, bytes32 sLock) public returns (bool success) {
require(msg.sender != homeAuthority);
require(msg.sender != homeValidator);
require(claimed[msg.sender][_depositBlock]);
require(!surrendered[msg.sender][_depositBlock]);
bytes32 _lockHash = keccak256(abi.encodePacked(msg.sender, _value, network, bridge, _depositBlock));
require(ecrecover(prefixed(_lockHash), vLock, rLock, sLock) == owner);
require(!surrendered[msg.sender][_depositBlock]); // not surrendered already
bytes32 _lockHash = keccak256(abi.encodePacked(msg.sender, _value, network, homeBridge, _depositBlock));
require(ecrecover(prefixed(_lockHash), vLock, rLock, sLock) == validator);
require(balances[msg.sender] >= _value);
surrendered[msg.sender][_depositBlock] = true; // withdraw tokens from circulation
balances[msg.sender] -= _value;
Expand All @@ -98,8 +106,6 @@ contract BridgeRGEToken is EIP20 {
return true;
}

// XXX to put in lib

function getHexString(bytes32 value) internal pure returns (string) {
bytes memory result = new bytes(64);
string memory characterString = "0123456789abcdef";
Expand All @@ -120,10 +126,10 @@ contract BridgeRGEToken is EIP20 {
event Repudiate(address indexed account, uint indexed _network, uint indexed depositBlock, uint8 v, bytes32 r, bytes32 s);

function repudiate(bytes32 _hash, address _account, uint _depositBlock, uint8 v, bytes32 r, bytes32 s)
onlyBy(owner) public {
onlyBy(validator) public {
require(msg.sender != _account);
require(surrendered[_account][_depositBlock]);
require(_hash == keccak256(abi.encodePacked(_account, network, bridge, _depositBlock)));
require(_hash == keccak256(abi.encodePacked(_account, network, homeBridge, _depositBlock)));
require(ecrecover(prefixed(_hash), v, r, s) == msg.sender);
emit Repudiate(_account, network, _depositBlock, v, r, s);
}
Expand Down
41 changes: 24 additions & 17 deletions contracts/RougeBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "./RGETokenInterface.sol";

contract RougeBridge {

string public version = 'v0.2';
string public version = 'v0.4';

address public owner;

Expand All @@ -26,12 +26,18 @@ contract RougeBridge {
rge = RGETokenInterface(_rge);
}

function newOwner(address _account) onlyBy(owner) public {
owner = _account;
}

mapping (uint => bool) public isOpen; /* is the bridge open for this network */
mapping (uint => address) public foreignAuthority; /* foreignAuthority network owner */
mapping (uint => address) public homeValidator; /* address to sign on home network */
mapping (uint => address) public foreignValidator; /* address to sign on foreign network */

function adminBridge(uint _network, bool _flag, address _foreignAuthority) onlyBy(owner) public {
function adminBridge(uint _network, bool _flag, address _homeValidator, address _foreignValidator) onlyBy(owner) public {
isOpen[_network] = _flag;
foreignAuthority[_network] = _foreignAuthority;
homeValidator[_network] = _homeValidator;
foreignValidator[_network] = _foreignValidator;
}

mapping (address => mapping (uint => mapping (uint => uint256))) public escrow;
Expand All @@ -42,10 +48,11 @@ contract RougeBridge {
/* caller need to approve RGE transfer beforehand */
function deposit(uint256 _value, uint _network) public {
require(isOpen[_network]);
require(msg.sender != owner);
require(msg.sender != homeValidator[_network]);
require(msg.sender != foreignValidator[_network]);
require(_value > 0);
require(escrow[msg.sender][_network][block.number] == 0); // only 1 deposit per block per user maximum (XXX use punitive assert ?)
assert(escrowSeal[msg.sender][_network][block.number] == bytes32(0)); // there can't be seal, but let's be sure
require(escrow[msg.sender][_network][block.number] == 0); // only 1 deposit per user/network/block maximum
assert(escrowSeal[msg.sender][_network][block.number] == bytes32(0)); // there can't be seal already, but let's double check
require(rge.transferFrom(msg.sender, this, _value)); // transfer tokens to this home bridge contract
emit BridgeDeposit(msg.sender, _network, block.number, _value);
escrow[msg.sender][_network][block.number] = _value;
Expand All @@ -54,9 +61,9 @@ contract RougeBridge {
event BridgeWithdraw(address indexed account, uint indexed _network, uint indexed depositBlock, uint256 value);

function withdraw(uint _network, uint _depositBlock) public {
require(escrowSeal[msg.sender][_network][_depositBlock] == bytes32(0)); // can't withdraw locked tokens (ie no seal)
require(escrowSeal[msg.sender][_network][_depositBlock] == bytes32(0)); // can't withdraw tokens locked with seal
uint256 _value = escrow[msg.sender][_network][_depositBlock];
require(_value > 0); // tokens should be in escrow
require(_value > 0); // tokens should still be in escrow
escrow[msg.sender][_network][_depositBlock] = 0;
require(rge.transfer(msg.sender, _value)); // transfer back tokens to sender
emit BridgeWithdraw(msg.sender, _network, _depositBlock, _value);
Expand All @@ -83,36 +90,36 @@ contract RougeBridge {
event EscrowLocked(address indexed account, uint indexed _network, uint indexed depositBlock, uint8 v, bytes32 r, bytes32 s, bytes32 sealHash);

function lockEscrow(bytes32 _hash, address _account, uint _network, uint _depositBlock, uint8 v, bytes32 r, bytes32 s)
onlyBy(owner) public {
onlyBy(homeValidator[_network]) public {
require(escrow[_account][_network][_depositBlock] > 0); // do not work for nothing
require(_hash == keccak256(abi.encodePacked(
_account, escrow[_account][_network][_depositBlock], _network, this, _depositBlock)));
require(ecrecover(prefixed(_hash), v, r, s) == foreignAuthority[_network]); // confirms that foreign authority has undersigned the tokens Locking
require(ecrecover(prefixed(_hash), v, r, s) == foreignValidator[_network]); // confirms that foreign authority has undersigned the tokens Locking
bytes32 sealHash = keccak256(abi.encodePacked(_hash, v, r, s, block.number));
escrowSeal[_account][_network][_depositBlock] = sealHash;
emit EscrowLocked(_account, _network, _depositBlock, v, r, s, sealHash);
}

// The seal needs to be signed again by RGE bridge owner to be used as authorization to claim token in foreign chain
// The seal needs to be signed again by RGE bridge validator to be used as authorization to claim token in foreign chain
// The user can claim the bridged tokens as soon as the authorization is visible on the main blockchain (via LOGS)
// (it's ok if the claiming tx comes before the authorization tx)

event BridgeAuth(address indexed account, uint indexed _network, uint indexed depositBlock, uint8 v, bytes32 r, bytes32 s);

function createAuth(address _account, uint _network, uint _depositBlock, uint8 v, bytes32 r, bytes32 s)
onlyBy(owner) public {
require(ecrecover(prefixed(escrowSeal[_account][_network][_depositBlock]), v, r, s) == owner);
onlyBy(homeValidator[_network]) public {
require(ecrecover(prefixed(escrowSeal[_account][_network][_depositBlock]), v, r, s) == homeValidator[_network]);
emit BridgeAuth(_account, _network, _depositBlock, v, r, s);
}

event EscrowUnlocked(address indexed account, uint indexed _network, uint indexed depositBlock);

function unlockEscrow(bytes32 _hash, address _account, uint _network, uint _depositBlock, uint8 v, bytes32 r, bytes32 s)
onlyBy(owner) public {
onlyBy(homeValidator[_network]) public {
require(escrow[_account][_network][_depositBlock] > 0);
escrowSeal[_account][_network][_depositBlock] != bytes32(0); // seal should exists
escrowSeal[_account][_network][_depositBlock] != bytes32(0); // seal should exist
require(_hash == keccak256(abi.encodePacked(_account, _network, this, _depositBlock)));
require(ecrecover(prefixed(_hash), v, r, s) == foreignAuthority[_network]);
require(ecrecover(prefixed(_hash), v, r, s) == foreignValidator[_network]);
escrowSeal[_account][_network][_depositBlock] = bytes32(0); // remove the seal
emit EscrowUnlocked(_account, _network, _depositBlock);
}
Expand Down
2 changes: 1 addition & 1 deletion migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = async function(deployer) {
deployer.deploy(TestRGEToken),
deployer.deploy(RougeFactory),
deployer.deploy(RougeBridge, '0x345ca3e014aaf5dca488057592ee47305d9b3e10'),
deployer.deploy(BridgeRGEToken, 3, '0x0', '0x0', 'Foreign RGE', 'f_RGE')
deployer.deploy(BridgeRGEToken, 3, '0x0', '0x0', '0x0', 'Foreign RGE', 'f_RGE')
]);

instances = await Promise.all([
Expand Down
51 changes: 41 additions & 10 deletions test/BridgeRGEToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ const getAccountSignature = function(hash, account) {

contract('BridgeRGEToken', function(accounts) {

it("bridged RGE is a correct RGE", async function() {

const user = accounts[1];

const foreign_network = 3;
const foreign_validator = accounts[3];

const home_validator = accounts[4];

const rge = await RGEToken.deployed();
const bridge = await RougeBridge.deployed();

const f_rge = await BridgeRGEToken.new(foreign_network, foreign_validator, bridge.address, home_validator, 'Foreign RGE', 'f_RGE');

const user_balance_before = await f_rge.balanceOf.call(user);
assert.equal(user_balance_before.toNumber(), 0, "user has no f_rge tokens to start with");

const name = await f_rge.name.call()
assert.equal(name, 'Foreign RGE', "correct name");

const symbol = await f_rge.symbol.call()
assert.equal(symbol, 'f_RGE', "correct symbol");



});


it("Claim tokens locked in home bridge", async function() {

// user need to use the SAME address on both chain.
Expand All @@ -48,7 +76,10 @@ contract('BridgeRGEToken', function(accounts) {
const deposit = 50 * 10**6; /* 1K RGE tokens */

const foreign_network = 3;
const foreign_authority = accounts[3];
const foreign_owner = accounts[2];
const foreign_validator = accounts[3];

const home_validator = accounts[4];

const rge = await RGEToken.deployed();
const bridge = await RougeBridge.deployed();
Expand All @@ -58,23 +89,23 @@ contract('BridgeRGEToken', function(accounts) {
await rge.giveMeRGE(tokens, {from: user});
await rge.approve(bridge.address, deposit, {from: user});

await bridge.adminBridge(foreign_network, true, foreign_authority)
await bridge.adminBridge(foreign_network, true, home_validator, foreign_validator)

const tx = await bridge.deposit(deposit, foreign_network, {from: user, gas: 67431 +20000, gasPrice: web3.toWei(1, "gwei")})
const depositBlock = tx.receipt.blockNumber;

const lockHash = createLockHash(user, deposit, foreign_network, bridge.address, depositBlock)
const signLock = getAccountSignature(lockHash, foreign_authority)
const lock_tx = await bridge.lockEscrow(lockHash, user, foreign_network, depositBlock, signLock.v, signLock.r, signLock.s);
const signLock = getAccountSignature(lockHash, foreign_validator)
const lock_tx = await bridge.lockEscrow(lockHash, user, foreign_network, depositBlock, signLock.v, signLock.r, signLock.s, {from: home_validator});
const lockBlock = lock_tx.receipt.blockNumber;
const expected_seal = createSealHash(lockHash, signLock.v, signLock.r, signLock.s, lockBlock);
const seal = await bridge.escrowSeal.call(user, foreign_network, depositBlock);
assert.equal(seal, expected_seal, "we got a correct seal");

// owner is create the Auth Hash (to be used on foreign chain)

const signAuth = getAccountSignature(seal, accounts[0]);
const auth_tx = await bridge.createAuth(user, foreign_network, depositBlock, signAuth.v, signAuth.r, signAuth.s);
const signAuth = getAccountSignature(seal, home_validator);
const auth_tx = await bridge.createAuth(user, foreign_network, depositBlock, signAuth.v, signAuth.r, signAuth.s, {from: home_validator});

const event_BridgeAuth_sign = web3.sha3('BridgeAuth(address,uint256,uint256,uint8,bytes32,bytes32)')
auth_tx.receipt.logs.forEach( function(e) {
Expand All @@ -95,7 +126,7 @@ contract('BridgeRGEToken', function(accounts) {

/* nb: for the sake of test, it's the same chain */

const f_rge = await BridgeRGEToken.new(foreign_network, bridge.address, accounts[0], 'Foreign RGE', 'f_RGE', {from: foreign_authority});
const f_rge = await BridgeRGEToken.new(foreign_network, foreign_validator, bridge.address, home_validator, 'Foreign RGE', 'f_RGE', {from: foreign_owner});

const user_balance_before = await f_rge.balanceOf.call(user);
assert.equal(user_balance_before.toNumber(), 0, "user has no f_rge tokens to start with");
Expand All @@ -114,14 +145,14 @@ contract('BridgeRGEToken', function(accounts) {


const hash2 = createUnlockHash(user, foreign_network, bridge.address, depositBlock)
const sign2 = getAccountSignature(hash2, foreign_authority)
const repudiate_tx = await f_rge.repudiate(hash2, user, depositBlock, sign2.v, sign2.r, sign2.s, {from: foreign_authority});
const sign2 = getAccountSignature(hash2, foreign_validator)
const repudiate_tx = await f_rge.repudiate(hash2, user, depositBlock, sign2.v, sign2.r, sign2.s, {from: foreign_validator});

/* ########## BACK ON HOME CHAIN ########## */

/* arguments from unlockEscrow can be read from foreign chain LOG Repudiate */

const unlock_tx = await bridge.unlockEscrow(hash2, user, foreign_network, depositBlock, sign2.v, sign2.r, sign2.s);
const unlock_tx = await bridge.unlockEscrow(hash2, user, foreign_network, depositBlock, sign2.v, sign2.r, sign2.s, {from: home_validator});

const seal_after = await bridge.escrowSeal.call(user, foreign_network, depositBlock);
assert.equal(seal_after, false, "the tokens are not locked anymore in the bridge contract");
Expand Down
Loading

0 comments on commit 6cb17ae

Please sign in to comment.