diff --git a/contracts b/contracts index efbbdfad..f35a2722 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit efbbdfade1d2eae6a6c6f12f1a59aa936470ad6f +Subproject commit f35a2722d3a9dd8b7456887b43aaf3143f78cf3b diff --git a/e2e-commons/constants.json b/e2e-commons/constants.json index 9f5c8334..f418fbe5 100644 --- a/e2e-commons/constants.json +++ b/e2e-commons/constants.json @@ -52,6 +52,7 @@ "foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9", "halfDuplexToken": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359", "saiTop": "0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3", + "chaiToken": "0x06af07097c9eeb7fd685c692751d5c66db49c215", "ui": "http://localhost:3002", "monitor": "http://monitor-erc20-native:3012/bridge" }, diff --git a/e2e-commons/up.sh b/e2e-commons/up.sh index ce3e1e3e..78ba308e 100755 --- a/e2e-commons/up.sh +++ b/e2e-commons/up.sh @@ -23,6 +23,7 @@ startValidator () { docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:transfer docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:half-duplex-transfer docker-compose $1 run $2 $3 -d oracle-erc20-native yarn worker:swap-tokens + docker-compose $1 run $2 $3 -d oracle-erc20-native yarn worker:convert-to-chai docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:signature-request docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:collected-signatures docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request @@ -47,6 +48,7 @@ while [ "$1" != "" ]; do 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-erc20-native yarn worker:convert-to-chai 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 05bd45c0..0b431390 100644 --- a/oracle-e2e/test/ercToNative.js +++ b/oracle-e2e/test/ercToNative.js @@ -465,4 +465,91 @@ describe('erc to native', () => { } }) }) + it('should not invest dai when chai token is disabled', async () => { + const bridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + + await foreignBridge.methods.setMinDaiTokenBalance(foreignWeb3.utils.toWei('2', 'ether')).send({ + from: validator.address, + gas: '1000000' + }) // set min limit for automatic investment to 2*2 dai + + const valueToTransfer = foreignWeb3.utils.toWei('5', 'ether') + + // this transfer won't trigger a call to convert to chai + await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + await promiseRetry(async (retry, number) => { + if (number < 4) { + retry() + } else { + const updatedBridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert( + toBN(bridgeDaiTokenBalance) + .add(toBN(valueToTransfer)) + .eq(toBN(updatedBridgeDaiTokenBalance)), + 'Dai tokens should not be when chai is disabled' + ) + } + }) + }) + it('should invest dai after enough tokens are collected on bridge account', async () => { + await foreignBridge.methods.initializeChaiToken().send({ + from: validator.address, + gas: '1000000' + }) // initialize chai token + await foreignBridge.methods.setMinDaiTokenBalance('0').send({ + from: validator.address, + gas: '1000000' + }) // set investing limit to 0 + await foreignBridge.methods.convertDaiToChai().send({ + from: validator.address, + gas: '1000000' + }) // convert all existing dai tokens on bridge account to chai, in order to start from zero balance + await foreignBridge.methods.setMinDaiTokenBalance(foreignWeb3.utils.toWei('2', 'ether')).send({ + from: validator.address, + gas: '1000000' + }) // set investing limit to 2 dai, automatically invest should happen after 4 dai + + const valueToTransfer = foreignWeb3.utils.toWei('3', 'ether') + + // this transfer won't trigger a call to convert to chai + await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + await promiseRetry(async (retry, number) => { + if (number < 4) { + retry() + } else { + const bridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert( + valueToTransfer === bridgeDaiTokenBalance, + 'Dai tokens should not be invested automatically before twice limit is reached' + ) + } + }) + + // this transfer will trigger call to convert to chai + await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({ + from: user.address, + gas: '1000000' + }) + + await promiseRetry(async retry => { + const updatedBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + if (toBN(updatedBalance).gte(toBN(valueToTransfer).add(toBN(valueToTransfer)))) { + retry() + } else { + const updatedBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call() + assert( + toBN(updatedBalance).eq(toBN(foreignWeb3.utils.toWei('2', 'ether'))), + 'Dai bridge balance should be equal to limit' + ) + } + }) + }) }) diff --git a/oracle/config/convert-to-chai-worker.config.js b/oracle/config/convert-to-chai-worker.config.js new file mode 100644 index 00000000..f30189c7 --- /dev/null +++ b/oracle/config/convert-to-chai-worker.config.js @@ -0,0 +1,20 @@ +const baseConfig = require('./base.config') +const { EXIT_CODES } = require('../src/utils/constants') + +const id = `${baseConfig.id}-convert-to-chai` + +const workerRequired = baseConfig.id === 'erc-native' + +if (!workerRequired) { + console.error(`Convert to chai tokens worker not required for bridge mode ${process.env.ORACLE_BRIDGE_MODE}`) + process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED) +} + +module.exports = { + ...baseConfig.bridgeConfig, + ...baseConfig.foreignConfig, + workerQueue: 'convert-to-chai', + senderQueue: 'foreign', + name: `worker-${id}`, + id +} diff --git a/oracle/config/transfer-watcher.config.js b/oracle/config/transfer-watcher.config.js index ce4d0575..76c30995 100644 --- a/oracle/config/transfer-watcher.config.js +++ b/oracle/config/transfer-watcher.config.js @@ -29,6 +29,11 @@ if (!transferWatcherRequired) { process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED) } +const workerQueueConfig = {} +if (baseConfig.id === 'erc-native') { + workerQueueConfig.workerQueue = 'convert-to-chai' +} + module.exports = { ...baseConfig.bridgeConfig, ...baseConfig.foreignConfig, @@ -37,6 +42,7 @@ module.exports = { eventAbi: ERC20_ABI, eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS }, queue: 'home', + ...workerQueueConfig, name: `watcher-${id}`, id } diff --git a/oracle/docker-compose-erc-native.yml b/oracle/docker-compose-erc-native.yml index 6cb838e4..655d7806 100644 --- a/oracle/docker-compose-erc-native.yml +++ b/oracle/docker-compose-erc-native.yml @@ -9,6 +9,7 @@ services: - net_rabbit_bridge_transfer - net_rabbit_bridge_half_duplex_transfer - net_rabbit_bridge_swap_tokens_worker + - net_rabbit_bridge_convert_to_chai_worker redis: extends: file: docker-compose.yml @@ -75,6 +76,18 @@ services: entrypoint: yarn worker:swap-tokens networks: - net_rabbit_bridge_swap_tokens_worker + bridge_convert_to_chai_worker: + cpus: 0.1 + mem_limit: 500m + image: poanetwork/tokenbridge-oracle:latest + env_file: ./.env + environment: + - NODE_ENV=production + - ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS} + restart: unless-stopped + entrypoint: yarn worker:convert-to-chai + networks: + - net_rabbit_bridge_convert_to_chai_worker bridge_senderhome: extends: file: docker-compose.yml @@ -121,3 +134,5 @@ networks: driver: bridge net_rabbit_bridge_senderforeign: driver: bridge + net_rabbit_bridge_convert_to_chai_worker: + driver: bridge diff --git a/oracle/package.json b/oracle/package.json index 83f168ce..bc564a0c 100644 --- a/oracle/package.json +++ b/oracle/package.json @@ -11,6 +11,7 @@ "watcher:transfer": "./scripts/start-worker.sh watcher transfer-watcher", "watcher:half-duplex-transfer": "./scripts/start-worker.sh watcher half-duplex-transfer-watcher", "worker:swap-tokens": "./scripts/start-worker.sh worker swap-tokens-worker", + "worker:convert-to-chai": "./scripts/start-worker.sh worker convert-to-chai-worker", "sender:home": "./scripts/start-worker.sh sender home-sender", "sender:foreign": "./scripts/start-worker.sh sender foreign-sender", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer,watcher:half-duplex-transfer, worker:swap-tokens, sender:home,sender:foreign' -c 'red,green,yellow,blue,white,gray,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn watcher:half-duplex-transfer' 'yarn worker:swap-tokens' 'yarn sender:home' 'yarn sender:foreign'", diff --git a/oracle/src/utils/chaiUtils.js b/oracle/src/utils/chaiUtils.js new file mode 100644 index 00000000..0d82c16e --- /dev/null +++ b/oracle/src/utils/chaiUtils.js @@ -0,0 +1,13 @@ +async function isChaiTokenEnabled(bridgeContract, logger) { + logger.debug('Checking Chai availability') + try { + return await bridgeContract.methods.isChaiTokenEnabled().call() + } catch (e) { + logger.debug('Method isChaiTokenEnabled is not supported') + return false + } +} + +module.exports = { + isChaiTokenEnabled +} diff --git a/oracle/src/watcher.js b/oracle/src/watcher.js index 37a9fd36..1687ac69 100644 --- a/oracle/src/watcher.js +++ b/oracle/src/watcher.js @@ -9,6 +9,7 @@ const rpcUrlsManager = require('./services/getRpcUrlsManager') const { getRequiredBlockConfirmations, getEvents } = require('./tx/web3') const { checkHTTPS, watchdog } = require('./utils/utils') const { EXIT_CODES } = require('./utils/constants') +const { isChaiTokenEnabled } = require('./utils/chaiUtils') if (process.argv.length < 3) { logger.error('Please check the number of arguments, config file was not provided') @@ -157,6 +158,15 @@ async function getLastBlockToProcess() { return lastBlockNumber.sub(requiredBlockConfirmations) } +async function isWorkerNeeded() { + switch (config.id) { + case 'erc-native-transfer': + return isChaiTokenEnabled(bridgeContract, logger) + default: + return true + } +} + async function main({ sendToQueue, sendToWorker }) { try { await checkConditions() @@ -186,7 +196,7 @@ async function main({ sendToQueue, sendToWorker }) { logger.info(`Found ${events.length} ${config.event} events`) if (events.length) { - if (sendToWorker) { + if (sendToWorker && (await isWorkerNeeded())) { await sendToWorker({ blockNumber: toBlock.toString() }) } diff --git a/oracle/src/worker.js b/oracle/src/worker.js index 17d7631d..773d8943 100644 --- a/oracle/src/worker.js +++ b/oracle/src/worker.js @@ -8,6 +8,7 @@ const { connectWorkerToQueue } = require('./services/amqpClient') const config = require(path.join('../config/', process.argv[2])) const swapTokens = require('./workers/swapTokens')(config) +const convertToChai = require('./workers/convertToChai')(config) async function initialize() { try { @@ -39,6 +40,8 @@ async function initialize() { async function run(blockNumber) { if (config.id === 'erc-native-swap-tokens') { return swapTokens(blockNumber) + } else if (config.id === 'erc-native-convert-to-chai') { + return convertToChai(blockNumber) } else { return [] } diff --git a/oracle/src/workers/convertToChai.js b/oracle/src/workers/convertToChai.js new file mode 100644 index 00000000..8b8900bf --- /dev/null +++ b/oracle/src/workers/convertToChai.js @@ -0,0 +1,80 @@ +require('../../env') +const { HttpListProviderError } = require('http-list-provider') +const rootLogger = require('../services/logger') +const { web3Foreign } = require('../services/web3') + +const { BRIDGE_VALIDATORS_ABI } = require('../../../commons') + +let validatorContract = null + +function convertToChaiBuilder(config) { + const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress) + return async function convertToChai(blockNumber) { + const txToSend = [] + + const logger = rootLogger.child({ + blockNumber: blockNumber.toString() + }) + + logger.debug(`Starting convert to chai operation`) + + if (validatorContract === null) { + logger.debug('Getting validator contract address') + const validatorContractAddress = await foreignBridge.methods.validatorContract().call() + logger.debug({ validatorContractAddress }, 'Validator contract address obtained') + + validatorContract = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress) + } + + logger.debug(`Checking if is validator duty`) + const validatorDuty = await validatorContract.methods.isValidatorDuty(config.validatorAddress).call() + + if (!validatorDuty) { + logger.info(`Convert to chai discarded because is not validator duty`) + return txToSend + } + + logger.debug(`Checking if dai token balance is above the threshold`) + const daiNeedsToBeInvested = await foreignBridge.methods.isDaiNeedsToBeInvested().call() + + if (!daiNeedsToBeInvested) { + logger.info(`Convert to chai discarded because dai balance is below the threshold or chai token is not set`) + return txToSend + } + + let gasEstimate + + try { + logger.debug(`Estimate gas`) + gasEstimate = await foreignBridge.methods.convertDaiToChai().estimateGas({ + from: config.validatorAddress + }) + + logger.debug({ gasEstimate }, 'Gas estimated') + } catch (e) { + if (e instanceof HttpListProviderError) { + const errorMsg = 'RPC Connection Error: convertToChai Gas Estimate cannot be obtained.' + logger.error(e, errorMsg) + throw new Error(errorMsg) + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e + } + } + + // generate data + const data = await foreignBridge.methods.convertDaiToChai().encodeABI() + + // push to job + txToSend.push({ + data, + gasEstimate, + transactionReference: `convert to chai operation for block number ${blockNumber.toString()}`, + to: config.foreignBridgeAddress + }) + + return txToSend + } +} + +module.exports = convertToChaiBuilder diff --git a/parity/chain-foreign.json b/parity/chain-foreign.json index a3c8ac88..d6c80e40 100644 --- a/parity/chain-foreign.json +++ b/parity/chain-foreign.json @@ -136,6 +136,13 @@ "0x0": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359", "0x1": "0xc4c7497fbe1a886841a195a5d622cd60053c1376" } + }, + "06af07097c9eeb7fd685c692751d5c66db49c215": { + "balance": "0", + "code": "0x6080604052600436106100565763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633b4da69f811461005b5780636c25b3461461008e578063be22f546146100ce575b600080fd5b34801561006757600080fd5b5061008c73ffffffffffffffffffffffffffffffffffffffff6004351660243561010c565b005b34801561009a57600080fd5b506100bc73ffffffffffffffffffffffffffffffffffffffff600435166101c5565b60408051918252519081900360200190f35b3480156100da57600080fd5b506100e36101cc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60008054604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101859052905173ffffffffffffffffffffffffffffffffffffffff909216926323b872dd926064808401936020939083900390910190829087803b15801561018d57600080fd5b505af11580156101a1573d6000803e3d6000fd5b505050506040513d60208110156101b757600080fd5b505060018054909101905550565b5060015490565b60005473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820d01d11b7ea4dad7896e3fbb5d06966bf715276ebd69a35990fa39c3158ca489d0029", + "storage": { + "0x0": "0x7cC4B1851c35959D34e635A470F6b5C43bA3C9c9" + } } } }