diff --git a/oracle-e2e/deploy.js b/oracle-e2e/deploy.js index 52fbea6a..ab67d97e 100644 --- a/oracle-e2e/deploy.js +++ b/oracle-e2e/deploy.js @@ -6,9 +6,12 @@ const envsDir = path.join(__dirname, 'envs') const deployContractsDir = path.join(__dirname, contractsPath, 'deploy') const erc20ScriptDir = path.join(__dirname, 'scripts') +// native-erc20 shell.cp(path.join(envsDir, 'contracts-deploy.env'), path.join(deployContractsDir, '.env')) shell.cd(deployContractsDir) shell.exec('node deploy.js') + +// erc20-erc20 shell.cd(erc20ScriptDir) shell.exec('node deployERC20.js') shell.cd(deployContractsDir) @@ -16,9 +19,17 @@ shell.rm('.env') shell.cp(path.join(envsDir, 'erc-contracts-deploy.env'), path.join(deployContractsDir, '.env')) shell.exec('node deploy.js') shell.rm('.env') + +// erc20-native shell.cp( path.join(envsDir, 'erc-native-contracts-deploy.env'), path.join(deployContractsDir, '.env') ) shell.exec('node src/utils/deployBlockReward.js') shell.exec('node deploy.js') + +// amb +shell.rm('.env') +shell.cp(path.join(envsDir, 'amb-contract-deploy.env'), path.join(deployContractsDir, '.env')) +shell.exec('node deploy.js') +shell.exec('node src/utils/deployTestBox.js') diff --git a/oracle-e2e/docker-compose.yml b/oracle-e2e/docker-compose.yml index 5b924365..e46e28c7 100644 --- a/oracle-e2e/docker-compose.yml +++ b/oracle-e2e/docker-compose.yml @@ -103,6 +103,33 @@ services: - FOREIGN_POLLING_INTERVAL=500 - ALLOW_HTTP=yes command: "true" + bridge-amb: + build: + context: .. + dockerfile: oracle/Dockerfile + environment: + - NODE_ENV=production + - BRIDGE_MODE=ARBITRARY_MESSAGE + - QUEUE_URL=amqp://rabbit + - REDIS_URL=redis://redis + - HOME_RPC_URL=http://parity1:8545 + - FOREIGN_RPC_URL=http://parity2:8545 + - HOME_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0 + - FOREIGN_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0 + - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 + - REDIS_LOCK_TTL=1000 + - HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - HOME_GAS_PRICE_SPEED_TYPE=standard + - HOME_GAS_PRICE_FALLBACK=1000000000 + - HOME_GAS_PRICE_UPDATE_INTERVAL=600000 + - FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - FOREIGN_GAS_PRICE_SPEED_TYPE=standard + - FOREIGN_GAS_PRICE_FALLBACK=10000000000 + - FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000 + - HOME_POLLING_INTERVAL=500 + - FOREIGN_POLLING_INTERVAL=500 + - ALLOW_HTTP=yes + command: "true" e2e: build: context: .. diff --git a/oracle-e2e/envs/amb-contract-deploy.env b/oracle-e2e/envs/amb-contract-deploy.env new file mode 100644 index 00000000..e28ba7f2 --- /dev/null +++ b/oracle-e2e/envs/amb-contract-deploy.env @@ -0,0 +1,27 @@ +BRIDGE_MODE=ARBITRARY_MESSAGE +DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 +HOME_DEPLOYMENT_GAS_PRICE=10000000000 +FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000 +GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 +DEPLOYMENT_GAS_LIMIT_EXTRA=0.2 + +HOME_RPC_URL=http://parity1:8545 +HOME_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_MAX_AMOUNT_PER_TX=8000000 +HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 +HOME_GAS_PRICE=1000000000 +HOME_AMB_SUBSIDIZED_MODE=false + +FOREIGN_RPC_URL=http://parity2:8545 +FOREIGN_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_VALIDATORS_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_MAX_AMOUNT_PER_TX=8000000 +FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 +FOREIGN_GAS_PRICE=10000000000 +FOREIGN_AMB_SUBSIDIZED_MODE=false + +REQUIRED_NUMBER_OF_VALIDATORS=1 +VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b diff --git a/oracle-e2e/run-tests.sh b/oracle-e2e/run-tests.sh index f482f5de..f2117e44 100755 --- a/oracle-e2e/run-tests.sh +++ b/oracle-e2e/run-tests.sh @@ -13,6 +13,9 @@ docker-compose run -d bridge-erc npm run watcher:affirmation-request docker-compose run -d bridge-erc-native npm run watcher:signature-request docker-compose run -d bridge-erc-native npm run watcher:collected-signatures docker-compose run -d bridge-erc-native npm run watcher:affirmation-request +docker-compose run -d bridge-amb npm run watcher:signature-request +docker-compose run -d bridge-amb npm run watcher:collected-signatures +docker-compose run -d bridge-amb npm run watcher:affirmation-request docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run e2e yarn workspace oracle-e2e run start diff --git a/oracle-e2e/test/amb.js b/oracle-e2e/test/amb.js new file mode 100644 index 00000000..0ce1af59 --- /dev/null +++ b/oracle-e2e/test/amb.js @@ -0,0 +1,378 @@ +const path = require('path') +const Web3 = require('web3') +const assert = require('assert') +const promiseRetry = require('promise-retry') +const { user, validator, contractsPath } = require('../constants.json') +const { generateNewBlock } = require('../utils/utils') +const { toBN } = Web3.utils + +const abisDir = path.join(__dirname, '..', contractsPath, 'build/contracts') + +const homeWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity1:8545')) +const foreignWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity2:8545')) + +const homeBridgeAddress = '0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0' +const foreignBridgeAddress = '0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0' + +const homeBoxAddress = '0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1' +const foreignBoxAddress = '0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1' + +homeWeb3.eth.accounts.wallet.add(user.privateKey) +foreignWeb3.eth.accounts.wallet.add(user.privateKey) + +homeWeb3.eth.accounts.wallet.add(validator.privateKey) +foreignWeb3.eth.accounts.wallet.add(validator.privateKey) + +const homeAbi = require(path.join(abisDir, 'HomeAMB.json')).abi +const foreignAbi = require(path.join(abisDir, 'ForeignAMB.json')).abi +const boxAbi = require(path.join(abisDir, 'Box.json')).abi + +const homeAMB = new homeWeb3.eth.Contract(homeAbi, homeBridgeAddress) +const homeBox = new homeWeb3.eth.Contract(boxAbi, homeBoxAddress) + +const foreignAMB = new foreignWeb3.eth.Contract(foreignAbi, foreignBridgeAddress) +const foreignBox = new foreignWeb3.eth.Contract(boxAbi, foreignBoxAddress) + +const oneEther = foreignWeb3.utils.toWei('1', 'ether') +const subsidizedHash = homeWeb3.utils.toHex('AMB-subsidized-mode') + +describe('arbitrary message bridging', () => { + describe('Home to Foreign', () => { + describe('Defrayal Mode', () => { + it('should be able to deposit funds for home sender', async () => { + const initialBalance = await foreignAMB.methods.balanceOf(homeBoxAddress).call() + assert(toBN(initialBalance).isZero(), 'Balance should be zero') + + await foreignAMB.methods.depositForContractSender(homeBoxAddress).send({ + from: user.address, + gas: '1000000', + value: oneEther + }) + + const balance = await foreignAMB.methods.balanceOf(homeBoxAddress).call() + assert(toBN(balance).eq(toBN(oneEther)), 'Balance should be one ether') + }) + it('should bridge message and take fees', async () => { + const newValue = 9 + + const initialValue = await foreignBox.methods.value().call() + assert(toBN(initialValue).isZero(), 'Value should be zero') + + const initialBalance = await foreignAMB.methods.balanceOf(homeBoxAddress).call() + assert(!toBN(initialBalance).isZero(), 'Balance should not be zero') + + const setValueTx = await homeBox.methods + .setValueOnOtherNetworkGasPrice( + newValue, + homeBridgeAddress, + foreignBoxAddress, + '1000000000' + ) + .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(homeWeb3, user.address) + + // The bridge should create a new transaction with a CollectedSignatures + // event so we generate another trivial transaction + await promiseRetry( + async retry => { + const lastBlockNumber = await homeWeb3.eth.getBlockNumber() + if (lastBlockNumber >= setValueTx.blockNumber + 2) { + await generateNewBlock(homeWeb3, user.address) + } else { + retry() + } + }, + { + forever: true, + factor: 1, + minTimeout: 500 + } + ) + + // check that value changed and balance decreased + await promiseRetry(async retry => { + const value = await foreignBox.methods.value().call() + const balance = await foreignAMB.methods.balanceOf(homeBoxAddress).call() + if (!toBN(value).eq(toBN(newValue)) || toBN(balance).gte(toBN(oneEther))) { + retry() + } + }) + }) + it('should be able to withdraw from deposit', async () => { + const initialBalance = await foreignAMB.methods.balanceOf(homeBoxAddress).call() + assert(!toBN(initialBalance).isZero(), 'Balance should not be zero') + + const initialUserBalance = toBN(await foreignWeb3.eth.getBalance(user.address)) + + const tx = await homeBox.methods + .withdrawFromDepositOnOtherNetworkGasPrice( + user.address, + homeBridgeAddress, + foreignBridgeAddress, + '1000000000' + ) + .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(homeWeb3, user.address) + + // The bridge should create a new transaction with a CollectedSignatures + // event so we generate another trivial transaction + await promiseRetry( + async retry => { + const lastBlockNumber = await homeWeb3.eth.getBlockNumber() + if (lastBlockNumber >= tx.blockNumber + 2) { + await generateNewBlock(homeWeb3, user.address) + } else { + retry() + } + }, + { + forever: true, + factor: 1, + minTimeout: 500 + } + ) + + // check that value changed and balance decreased + await promiseRetry(async retry => { + const userBalance = toBN(await foreignWeb3.eth.getBalance(user.address)) + const boxBalance = toBN(await foreignAMB.methods.balanceOf(homeBoxAddress).call()) + if (!boxBalance.isZero() || userBalance.lte(initialUserBalance)) { + retry() + } + }) + }) + }) + describe('Subsidized Mode', () => { + it('should bridge message without taking fees', async () => { + const newValue = 3 + + await homeAMB.methods.setSubsidizedModeForHomeToForeign().send({ + from: validator.address, + gas: '1000000' + }) + const homeMode = await homeAMB.methods.homeToForeignMode().call() + + await foreignAMB.methods.setSubsidizedModeForHomeToForeign().send({ + from: validator.address, + gas: '1000000' + }) + const foreignMode = await foreignAMB.methods.homeToForeignMode().call() + + assert(homeMode === subsidizedHash, 'home mode incorrect') + assert(foreignMode === subsidizedHash, 'foreign mode incorrect') + + const initialValue = await foreignBox.methods.value().call() + assert( + !toBN(initialValue).eq(toBN(newValue)), + 'initial value should be different from new value' + ) + + const setValueTx = await homeBox.methods + .setValueOnOtherNetworkGasPrice( + newValue, + homeBridgeAddress, + foreignBoxAddress, + '1000000000' + ) + .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(homeWeb3, user.address) + + // The bridge should create a new transaction with a CollectedSignatures + // event so we generate another trivial transaction + await promiseRetry( + async retry => { + const lastBlockNumber = await homeWeb3.eth.getBlockNumber() + if (lastBlockNumber >= setValueTx.blockNumber + 2) { + await generateNewBlock(homeWeb3, user.address) + } else { + retry() + } + }, + { + forever: true, + factor: 1, + minTimeout: 500 + } + ) + + // check that value changed and balance decreased + await promiseRetry(async retry => { + const value = await foreignBox.methods.value().call() + if (!toBN(value).eq(toBN(newValue))) { + retry() + } + }) + }) + }) + }) + describe('Foreign to Home', () => { + describe('Defrayal Mode', () => { + it('should be able to deposit funds for foreign sender', async () => { + const initialBalance = await homeAMB.methods.balanceOf(foreignBoxAddress).call() + assert(toBN(initialBalance).isZero(), 'Balance should be zero') + + await homeAMB.methods.depositForContractSender(homeBoxAddress).send({ + from: user.address, + gas: '1000000', + value: oneEther + }) + + const balance = await homeAMB.methods.balanceOf(foreignBoxAddress).call() + assert(toBN(balance).eq(toBN(oneEther)), 'Balance should be one ether') + }) + it('should bridge message and take fees', async () => { + const newValue = 6 + + const initialValue = await homeBox.methods.value().call() + assert(toBN(initialValue).isZero(), 'Value should be zero') + + const initialBalance = await homeAMB.methods.balanceOf(foreignBoxAddress).call() + assert(!toBN(initialBalance).isZero(), 'Balance should not be zero') + + await foreignBox.methods + .setValueOnOtherNetworkGasPrice( + newValue, + foreignBridgeAddress, + homeBoxAddress, + '1000000000' + ) + .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 value changed and balance decreased + await promiseRetry(async retry => { + const value = await homeBox.methods.value().call() + const balance = await homeAMB.methods.balanceOf(foreignBoxAddress).call() + if (!toBN(value).eq(toBN(newValue)) || toBN(balance).gte(toBN(oneEther))) { + retry() + } + }) + }) + it('should be able to withdraw from deposit', async () => { + const initialBalance = await homeAMB.methods.balanceOf(foreignBoxAddress).call() + assert(!toBN(initialBalance).isZero(), 'Balance should not be zero') + + const initialUserBalance = toBN(await homeWeb3.eth.getBalance(user.address)) + + await foreignBox.methods + .withdrawFromDepositOnOtherNetworkGasPrice( + user.address, + foreignBridgeAddress, + homeBridgeAddress, + '1000000000' + ) + .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 value changed and balance decreased + await promiseRetry(async retry => { + const userBalance = toBN(await homeWeb3.eth.getBalance(user.address)) + const boxBalance = toBN(await homeAMB.methods.balanceOf(foreignBoxAddress).call()) + if (!boxBalance.isZero() || userBalance.lte(initialUserBalance)) { + retry() + } + }) + }) + }) + describe('Subsidized Mode', () => { + it('should bridge message without taking fees', async () => { + const newValue = 7 + + await homeAMB.methods.setSubsidizedModeForForeignToHome().send({ + from: validator.address, + gas: '1000000' + }) + + const homeMode = await homeAMB.methods.foreignToHomeMode().call() + + await foreignAMB.methods.setSubsidizedModeForForeignToHome().send({ + from: validator.address, + gas: '1000000' + }) + + const foreignMode = await foreignAMB.methods.foreignToHomeMode().call() + + assert(homeMode === subsidizedHash, 'home mode incorrect') + assert(foreignMode === subsidizedHash, 'foreign mode incorrect') + + const initialValue = await homeBox.methods.value().call() + assert( + !toBN(initialValue).eq(toBN(newValue)), + 'initial value should be different from new value' + ) + + await foreignBox.methods + .setValueOnOtherNetworkGasPrice( + newValue, + homeBridgeAddress, + foreignBoxAddress, + '1000000000' + ) + .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 value changed and balance decreased + await promiseRetry(async retry => { + const value = await homeBox.methods.value().call() + if (!toBN(value).eq(toBN(newValue))) { + retry() + } + }) + }) + }) + }) +})