diff --git a/e2e-commons/contracts-envs/erc-to-native.env b/e2e-commons/contracts-envs/erc-to-native.env index a91f5b20..f7991673 100644 --- a/e2e-commons/contracts-envs/erc-to-native.env +++ b/e2e-commons/contracts-envs/erc-to-native.env @@ -32,7 +32,7 @@ FOREIGN_GAS_PRICE=10000000000 FOREIGN_REWARDABLE=false BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977 -ERC20_TOKEN_ADDRESS=0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9 +ERC20_TOKEN_ADDRESS=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359 REQUIRED_NUMBER_OF_VALIDATORS=1 VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/oracle-e2e/test/ercToNative.js b/oracle-e2e/test/ercToNative.js index df17d9c8..ba474693 100644 --- a/oracle-e2e/test/ercToNative.js +++ b/oracle-e2e/test/ercToNative.js @@ -33,12 +33,122 @@ describe('erc to native', () => { before(async () => { halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call() halfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, halfDuplexTokenAddress) + }) + it('should continue working after migration', async () => { + const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address) - // set min threshold for swap + const transferValue = homeWeb3.utils.toWei('0.01') + + // erc20 token address and half duplex address are the same before migration + const tokenAddress = await foreignBridge.methods.erc20token().call() + + const erc20AndhalfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, tokenAddress) + + // send tokens to foreign bridge + await erc20AndhalfDuplexToken.methods + .transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue) + .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, number) => { + const balance = await homeWeb3.eth.getBalance(user.address) + await generateNewBlock(foreignWeb3, user.address) + // retry at least 4 times to check transfer is not double processed by the two watchers + if (toBN(balance).lte(toBN(originalBalanceOnHome)) || number < 4) { + retry() + } else { + assert( + toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(transferValue))), + 'User balance should be increased only by second transfer' + ) + } + }) + + // call migration + await foreignBridge.methods.migrateToMCD().send({ + from: validator.address, + gas: '4000000' + }) + + // update min threshold for swap await foreignBridge.methods.setMinHDTokenBalance(foreignWeb3.utils.toWei('1', 'ether')).send({ from: validator.address, gas: '1000000' }) + + const AfterMigrateBalance = await homeWeb3.eth.getBalance(user.address) + + // send tokens to foreign bridge + await erc20Token.methods + .transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue) + .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, number) => { + const balance = await homeWeb3.eth.getBalance(user.address) + await generateNewBlock(foreignWeb3, user.address) + // retry at least 4 times to check transfer is not double processed by the two watchers + if (toBN(balance).lte(toBN(AfterMigrateBalance)) || number < 4) { + retry() + } else { + assert( + toBN(balance).eq(toBN(AfterMigrateBalance).add(toBN(transferValue))), + 'User balance should be increased only by second transfer' + ) + } + }) + + const afterMigrateAndTransferBalance = await homeWeb3.eth.getBalance(user.address) + + // send tokens to foreign bridge + await halfDuplexToken.methods + .transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue) + .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, number) => { + const balance = await homeWeb3.eth.getBalance(user.address) + await generateNewBlock(foreignWeb3, user.address) + // retry at least 4 times to check transfer is not double processed by the two watchers + if (toBN(balance).lte(toBN(afterMigrateAndTransferBalance)) || number < 4) { + retry() + } else { + assert( + toBN(balance).eq(toBN(afterMigrateAndTransferBalance).add(toBN(transferValue))), + 'User balance should be increased only by second transfer' + ) + } + }) }) it('should convert tokens in foreign to coins in home', async () => { const balance = await erc20Token.methods.balanceOf(user.address).call() @@ -116,7 +226,6 @@ describe('erc to native', () => { 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') @@ -176,7 +285,9 @@ describe('erc to native', () => { assert(toBN(updatedBalance).isZero(), 'Half duplex bridge balance should be zero') assert( toBN(updatedBridgeErc20TokenBalance).eq( - toBN(bridgeErc20TokenBalance).add(toBN(foreignWeb3.utils.toWei('2', 'ether'))) + toBN(bridgeErc20TokenBalance) + .add(toBN(bridgeHalfDuplexBalance)) + .add(toBN(foreignWeb3.utils.toWei('2', 'ether'))) ), 'Erc20 token balance should be correctly increased by the token swap' ) diff --git a/oracle/scripts/initialChecks.js b/oracle/scripts/initialChecks.js index 70ba55bf..4508f0e2 100644 --- a/oracle/scripts/initialChecks.js +++ b/oracle/scripts/initialChecks.js @@ -1,5 +1,6 @@ require('../env') const Web3 = require('web3') +const { getTokensState } = require('../src/events/processTransfers/tokenState') const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_ERC_ABI, @@ -9,7 +10,7 @@ const { async function initialChecks() { const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env - const result = {} + let result = {} const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)) if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') { @@ -17,17 +18,7 @@ async function initialChecks() { result.bridgeableTokenAddress = await bridge.methods.erc20token().call() } else if (ORACLE_BRIDGE_MODE === 'ERC_TO_NATIVE') { const bridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS) - result.bridgeableTokenAddress = await bridge.methods.erc20token().call() - try { - const halfDuplexErc20tokenAddress = await bridge.methods.halfDuplexErc20token().call() - if (halfDuplexErc20tokenAddress !== result.bridgeableTokenAddress) { - result.halfDuplexTokenAddress = halfDuplexErc20tokenAddress - } else { - result.idle = true - } - } catch (e) { - result.idle = true - } + result = await getTokensState(bridge) } if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') { diff --git a/oracle/src/events/processTransfers/tokenState.js b/oracle/src/events/processTransfers/tokenState.js new file mode 100644 index 00000000..36e4c648 --- /dev/null +++ b/oracle/src/events/processTransfers/tokenState.js @@ -0,0 +1,20 @@ +async function getTokensState(bridgeContract) { + const context = {} + context.bridgeableTokenAddress = await bridgeContract.methods.erc20token().call() + try { + const halfDuplexErc20tokenAddress = await bridgeContract.methods.halfDuplexErc20token().call() + if (halfDuplexErc20tokenAddress !== context.bridgeableTokenAddress) { + context.halfDuplexTokenAddress = halfDuplexErc20tokenAddress + } else { + context.idle = true + } + } catch (e) { + context.idle = true + } + + return context +} + +module.exports = { + getTokensState +} diff --git a/oracle/src/watcher.js b/oracle/src/watcher.js index 8a310230..74bdf434 100644 --- a/oracle/src/watcher.js +++ b/oracle/src/watcher.js @@ -26,12 +26,16 @@ const processAMBSignatureRequests = require('./events/processAMBSignatureRequest const processAMBCollectedSignatures = require('./events/processAMBCollectedSignatures')(config) const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config) +const { getTokensState } = require('./events/processTransfers/tokenState') + const ZERO = toBN(0) const ONE = toBN(1) const web3Instance = config.web3 const bridgeContract = new web3Instance.eth.Contract(config.bridgeAbi, config.bridgeContractAddress) -const eventContract = new web3Instance.eth.Contract(config.eventAbi, config.eventContractAddress) +let { eventContractAddress } = config +let eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress) +let skipEvents = config.idle const lastBlockRedisKey = `${config.id}:lastProcessedBlock` let lastProcessedBlock = BN.max(config.startBlock.sub(ONE), ZERO) @@ -117,6 +121,29 @@ function processEvents(events, blockNumber) { } } +async function checkConditions() { + let state + switch (config.id) { + case 'erc-native-transfer': + state = await getTokensState(bridgeContract) + updateEventContract(state.bridgeableTokenAddress) + break + case 'erc-native-half-duplex-transfer': + state = await getTokensState(bridgeContract) + skipEvents = state.idle + updateEventContract(state.halfDuplexTokenAddress) + break + default: + } +} + +function updateEventContract(address) { + if (eventContractAddress !== address) { + eventContractAddress = address + eventContract = new web3Instance.eth.Contract(config.eventAbi, eventContractAddress) + } +} + async function getLastBlockToProcess() { const lastBlockNumberPromise = getBlockNumber(web3Instance).then(toBN) const requiredBlockConfirmationsPromise = getRequiredBlockConfirmations(bridgeContract).then(toBN) @@ -130,7 +157,9 @@ async function getLastBlockToProcess() { async function main({ sendToQueue, sendToWorker }) { try { - if (config.idle) { + await checkConditions() + + if (skipEvents) { logger.debug('Watcher in idle mode, skipping getting events') return }