diff --git a/commons/abis.js b/commons/abis.js index 03a4fc3f..c3202f66 100644 --- a/commons/abis.js +++ b/commons/abis.js @@ -13,6 +13,7 @@ const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/Rewardab const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi const FOREIGN_AMB_ABI = require('../contracts/build/contracts/ForeignAMB').abi const BOX_ABI = require('../contracts/build/contracts/Box').abi +const SAI_TOP = require('../contracts/build/contracts/SaiTopMock').abi const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis') const { BRIDGE_MODES } = require('./constants') @@ -92,5 +93,6 @@ module.exports = { ERC20_BYTES32_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI, - BOX_ABI + BOX_ABI, + SAI_TOP } diff --git a/e2e-commons/constants.json b/e2e-commons/constants.json index cc5079ca..aa8b552d 100644 --- a/e2e-commons/constants.json +++ b/e2e-commons/constants.json @@ -11,10 +11,6 @@ "address": "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b", "privateKey": "0x8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" }, - "owner": { - "address": "0x379a38a225fadb4a3769391ab26e2440681bf098", - "privateKey": "0x19fba401d77e4113b15095e9aa7117bcd25adcfac7f6111f8298894eef443600" - }, "blockGenerator": { "address": "0xB4579fd5AfEaB60B03Db3F408AAdD315035943f7", "privateKey": "0xd6143d390d8b28c33601bb0fe29392fb1c35c24ccfe8722c09c2bdd6ada2699f" @@ -38,6 +34,7 @@ "home": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda", "foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda", "foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9", + "saiTop": "0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3", "ui": "http://localhost:3002", "monitor": "http://monitor-erc20-native:3012" }, diff --git a/e2e-commons/up.sh b/e2e-commons/up.sh index 86563fc8..5a8053b3 100755 --- a/e2e-commons/up.sh +++ b/e2e-commons/up.sh @@ -22,6 +22,8 @@ while [ "$1" != "" ]; do docker-compose run -d oracle-erc20-native yarn watcher:collected-signatures docker-compose run -d oracle-erc20-native yarn watcher:affirmation-request docker-compose run -d oracle-erc20-native yarn watcher:transfer + docker-compose run -d oracle-erc20-native yarn watcher:half-duplex-transfer + docker-compose run -d oracle-erc20-native yarn worker:swap-tokens docker-compose run -d oracle-amb yarn watcher:signature-request docker-compose run -d oracle-amb yarn watcher:collected-signatures docker-compose run -d oracle-amb yarn watcher:affirmation-request diff --git a/oracle-e2e/test/ercToNative.js b/oracle-e2e/test/ercToNative.js index dd548322..df17d9c8 100644 --- a/oracle-e2e/test/ercToNative.js +++ b/oracle-e2e/test/ercToNative.js @@ -1,8 +1,15 @@ const Web3 = require('web3') const assert = require('assert') const promiseRetry = require('promise-retry') -const { user, secondUser, ercToNativeBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json') -const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons') +const { + user, + secondUser, + validator, + ercToNativeBridge, + homeRPC, + foreignRPC +} = require('../../e2e-commons/constants.json') +const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI, SAI_TOP } = require('../../commons') const { generateNewBlock } = require('../../e2e-commons/utils') const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL)) @@ -15,11 +22,24 @@ const { toBN } = foreignWeb3.utils homeWeb3.eth.accounts.wallet.add(user.privateKey) foreignWeb3.eth.accounts.wallet.add(user.privateKey) +foreignWeb3.eth.accounts.wallet.add(validator.privateKey) const erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToNativeBridge.foreignToken) const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS) describe('erc to native', () => { + let halfDuplexTokenAddress + let halfDuplexToken + before(async () => { + halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call() + halfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, halfDuplexTokenAddress) + + // set min threshold for swap + await foreignBridge.methods.setMinHDTokenBalance(foreignWeb3.utils.toWei('1', 'ether')).send({ + from: validator.address, + gas: '1000000' + }) + }) it('should convert tokens in foreign to coins in home', async () => { const balance = await erc20Token.methods.balanceOf(user.address).call() const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address) @@ -92,6 +112,237 @@ describe('erc to native', () => { } }) }) + it('should convert half duplex token in foreign to native token in home', async () => { + const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address) + const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert(toBN(bridgeHalfDuplexBalance).isZero(), 'Bridge balance should be zero') + + const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether') + + // this transfer won't trigger a call to swap tokens + await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + // Send a trivial transaction to generate a new block since the watcher + // is configured to wait 1 confirmation block + await generateNewBlock(foreignWeb3, user.address) + + // check that balance increases + await promiseRetry(async retry => { + const balance = await homeWeb3.eth.getBalance(user.address) + if (toBN(balance).lte(toBN(originalBalanceOnHome))) { + retry() + } else { + assert( + toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(valueToTransfer))), + 'User balance should be increased by the half duplex token transfer' + ) + } + }) + + const updatedBalanceOnHome = await homeWeb3.eth.getBalance(user.address) + const updatedBridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert( + toBN(updatedBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))), + 'Bridge balance should reflect the transfer value' + ) + + // this transfer will trigger call to swap tokens + await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + await generateNewBlock(foreignWeb3, user.address) + + await promiseRetry(async retry => { + const userBalance = await homeWeb3.eth.getBalance(user.address) + const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + + if ( + toBN(userBalance).lte(toBN(updatedBalanceOnHome)) || + toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance)) + ) { + retry() + } else { + assert( + toBN(userBalance).eq(toBN(updatedBalanceOnHome).add(toBN(valueToTransfer))), + 'User balance should be increased by the half duplex token transfer' + ) + const updatedBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert(toBN(updatedBalance).isZero(), 'Half duplex bridge balance should be zero') + assert( + toBN(updatedBridgeErc20TokenBalance).eq( + toBN(bridgeErc20TokenBalance).add(toBN(foreignWeb3.utils.toWei('2', 'ether'))) + ), + 'Erc20 token balance should be correctly increased by the token swap' + ) + } + }) + }) + it('should convert half duplex token in foreign to native token in home for alternative receiver ', async () => { + const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address) + const initialBalanceSecondUser = await homeWeb3.eth.getBalance(secondUser.address) + const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + + const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether') + + // approve tokens to foreign bridge + await halfDuplexToken.methods + .approve(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer) + .send({ + from: user.address, + gas: '1000000' + }) + .catch(e => { + console.error(e) + }) + + // call bridge method to transfer tokens to a different recipient + await foreignBridge.methods['relayTokens(address,uint256,address)']( + secondUser.address, + valueToTransfer, + halfDuplexTokenAddress + ) + .send({ + from: user.address, + gas: '1000000' + }) + .catch(e => { + console.error(e) + }) + + // Send a trivial transaction to generate a new block since the watcher + // is configured to wait 1 confirmation block + await generateNewBlock(foreignWeb3, user.address) + + // check that balance increases + await promiseRetry(async retry => { + const secondUserbalance = await homeWeb3.eth.getBalance(secondUser.address) + const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + const userbalance = await homeWeb3.eth.getBalance(user.address) + assert(toBN(userbalance).lte(toBN(originalBalanceOnHome)), 'User balance should be the same') + if ( + toBN(secondUserbalance).lte(toBN(initialBalanceSecondUser)) || + toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance)) + ) { + retry() + } else { + assert( + toBN(secondUserbalance).eq(toBN(initialBalanceSecondUser).add(toBN(valueToTransfer))), + 'User balance should be increased by the half duplex token transfer' + ) + const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero') + assert( + toBN(updatedBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance).add(toBN(valueToTransfer))), + 'Erc20 token balance should be correctly increased by the token swap' + ) + } + }) + }) + it('should not relay half duplex token transfer after Emergency Shutdown', async () => { + const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address) + const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + + const block = await foreignWeb3.eth.getBlock('latest') + const saiTop = new foreignWeb3.eth.Contract(SAI_TOP, ercToNativeBridge.saiTop) + + // Trigger Emergency Shutdown + await saiTop.methods + .setCaged(block.timestamp) + .send({ + from: user.address, + gas: '1000000' + }) + .catch(e => { + console.error(e) + }) + + const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether') + + await generateNewBlock(foreignWeb3, user.address) + + // this transfer won't trigger a call to swap tokens + await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + // Send a trivial transaction to generate a new block since the watcher + // is configured to wait 1 confirmation block + await generateNewBlock(foreignWeb3, user.address) + + // check that transfer and swap are not processed in the next blocks. + await promiseRetry(async (retry, number) => { + const balanceOnHome = await homeWeb3.eth.getBalance(user.address) + const currentBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + const currentBridgeHalfDuplexBalance = await halfDuplexToken.methods + .balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS) + .call() + + assert(toBN(balanceOnHome).eq(toBN(originalBalanceOnHome)), 'User balance should be the same') + assert( + toBN(currentBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))), + 'Half duplex balance should be the value of transfer' + ) + assert(toBN(currentBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance)), 'erc20 balance should not change') + + // generate new blocks + await generateNewBlock(foreignWeb3, user.address) + + // after several retries, the state is corrects + if (number < 4) { + retry() + } + }) + + // let's undo the Emergency Shutdown to check that the oracle is still working + await saiTop.methods.setCaged('0').send({ + from: user.address, + gas: '1000000' + }) + + const newValueToTransfer = foreignWeb3.utils.toWei('2', 'ether') + + await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, newValueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + await generateNewBlock(foreignWeb3, user.address) + + await promiseRetry(async retry => { + const userBalance = await homeWeb3.eth.getBalance(user.address) + const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + + if ( + toBN(userBalance).lte(toBN(originalBalanceOnHome)) || + toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance)) + ) { + retry() + } else { + assert( + toBN(userBalance).eq(toBN(originalBalanceOnHome).add(toBN(newValueToTransfer))), + 'User balance should be increased by the half duplex token transfer' + ) + const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero') + assert( + toBN(updatedBridgeErc20TokenBalance).eq( + toBN(bridgeErc20TokenBalance) + .add(toBN(valueToTransfer)) + .add(toBN(newValueToTransfer)) + ), + 'Erc20 token balance should be correctly increased by the token swap' + ) + } + }) + }) it('should convert coins in home to tokens in foreign', async () => { const originalBalance = await erc20Token.methods.balanceOf(user.address).call() diff --git a/parity/chain-foreign.json b/parity/chain-foreign.json index cdc88176..67f2cc60 100644 --- a/parity/chain-foreign.json +++ b/parity/chain-foreign.json @@ -106,7 +106,7 @@ "0x1": "0x4441490000000000000000000000000000000000000000000000000000000006", "0x2": "0x12", "0x4": "0x00000000000000000000000000000000000000000000001043561a8829300000", - "0x6": "0x379a38a225fadb4a3769391ab26e2440681bf098", + "0x6": "0xc73e0383F3Aff3215E6f04B0331D58CeCf0Ab849", "0xe72abea4d7a02a1dd3bab9806f38066f472bc02778742ed50ed69de48da9c3e6" : "0x0000000000000000000000000000000000000000000000056bc75e2d63100000", "0xc19ddbb1e237d15aa362708548b7cbc089c94e4977af9e54c2f09f0ae28a35fc" : "0x0000000000000000000000000000000000000000000000056bc75e2d63100000", "0xca2b12ce800b15ade60b8e814bbbf66b91734af718ac5562e6b8adc9b4e5b629" : "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"