From 62f9a080c96c9461fecda249343fa69cd2efee8d Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 18 May 2020 17:36:52 -0300 Subject: [PATCH] Add support for STAKE token ERC677-to-ERC677 through AMB mode in Bridge UI (#328) --- commons/abis.js | 16 +- commons/constants.js | 5 +- commons/test/constants.test.js | 2 +- commons/test/getTokenType.js | 95 +++++++++ commons/utils.js | 34 ++- contracts | 2 +- ui/src/components/Bridge.js | 8 +- ui/src/components/BridgeStatistics.js | 11 +- ui/src/components/FeeStatistics.js | 4 +- ui/src/components/NetworkDetails.js | 11 +- ui/src/components/StatisticsPage.js | 20 +- ui/src/stores/ForeignStore.js | 201 ++++++++++++------ ui/src/stores/HomeStore.js | 284 +++++++++++++++++--------- ui/src/stores/utils/contract.js | 13 +- ui/src/stores/utils/rewardable.js | 9 + 15 files changed, 529 insertions(+), 186 deletions(-) diff --git a/commons/abis.js b/commons/abis.js index c3202f66..0d67902e 100644 --- a/commons/abis.js +++ b/commons/abis.js @@ -7,13 +7,17 @@ const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignB const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi const ERC677_ABI = require('../contracts/build/contracts/ERC677').abi const ERC677_BRIDGE_TOKEN_ABI = require('../contracts/build/contracts/ERC677BridgeToken').abi -const BLOCK_REWARD_ABI = require('../contracts/build/contracts/IBlockReward').abi +const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockReward').abi const BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/RewardableValidators').abi 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_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeAMBErc677ToErc677').abi +const FOREIGN_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignAMBErc677ToErc677').abi +const HOME_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeStakeTokenMediator').abi +const FOREIGN_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignStakeTokenMediator').abi const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis') const { BRIDGE_MODES } = require('./constants') @@ -67,6 +71,12 @@ function getBridgeABIs(bridgeMode) { } else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) { HOME_ABI = HOME_AMB_ABI FOREIGN_ABI = FOREIGN_AMB_ABI + } else if (bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC) { + HOME_ABI = HOME_AMB_ERC_TO_ERC_ABI + FOREIGN_ABI = FOREIGN_AMB_ERC_TO_ERC_ABI + } else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) { + HOME_ABI = HOME_STAKE_ERC_TO_ERC_ABI + FOREIGN_ABI = FOREIGN_STAKE_ERC_TO_ERC_ABI } else { throw new Error(`Unrecognized bridge mode: ${bridgeMode}`) } @@ -94,5 +104,7 @@ module.exports = { HOME_AMB_ABI, FOREIGN_AMB_ABI, BOX_ABI, - SAI_TOP + SAI_TOP, + HOME_STAKE_ERC_TO_ERC_ABI, + FOREIGN_STAKE_ERC_TO_ERC_ABI } diff --git a/commons/constants.js b/commons/constants.js index fe2304b5..42154457 100644 --- a/commons/constants.js +++ b/commons/constants.js @@ -3,7 +3,9 @@ const BRIDGE_MODES = { ERC_TO_ERC: 'ERC_TO_ERC', ERC_TO_NATIVE: 'ERC_TO_NATIVE', NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1', - ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE' + ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE', + AMB_ERC_TO_ERC: 'AMB_ERC_TO_ERC', + STAKE_AMB_ERC_TO_ERC: 'STAKE_AMB_ERC_TO_ERC' } const ERC_TYPES = { @@ -14,6 +16,7 @@ const ERC_TYPES = { const FEE_MANAGER_MODE = { ONE_DIRECTION: 'ONE_DIRECTION', BOTH_DIRECTIONS: 'BOTH_DIRECTIONS', + ONE_DIRECTION_STAKE: 'ONE_DIRECTION_STAKE', UNDEFINED: 'UNDEFINED' } diff --git a/commons/test/constants.test.js b/commons/test/constants.test.js index cc986e19..d29a4346 100644 --- a/commons/test/constants.test.js +++ b/commons/test/constants.test.js @@ -3,7 +3,7 @@ const { BRIDGE_MODES, ERC_TYPES } = require('../constants') describe('constants', () => { it('should contain correct number of bridge types', () => { - expect(Object.keys(BRIDGE_MODES).length).to.be.equal(5) + expect(Object.keys(BRIDGE_MODES).length).to.be.equal(7) }) it('should contain correct number of erc types', () => { diff --git a/commons/test/getTokenType.js b/commons/test/getTokenType.js index 03e8e228..664d43b2 100644 --- a/commons/test/getTokenType.js +++ b/commons/test/getTokenType.js @@ -61,4 +61,99 @@ describe('getTokenType', () => { // Then expect(type).to.equal(ERC_TYPES.ERC20) }) + + it('should return ERC20 if bridgeContract and isBridge are not present', async () => { + // Given + const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26' + const contract = { + methods: { + bridgeContract: () => { + return { + call: () => Promise.reject() + } + }, + isBridge: () => { + return { + call: () => Promise.reject() + } + } + } + } + + // When + const type = await getTokenType(contract, bridgeAddress) + + // Then + expect(type).to.equal(ERC_TYPES.ERC20) + }) + + it('should return ERC677 if isBridge returns true', async () => { + // Given + const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26' + const contract = { + methods: { + bridgeContract: () => { + return { + call: () => Promise.reject() + } + }, + isBridge: () => { + return { + call: () => Promise.resolve(true) + } + } + } + } + + // When + const type = await getTokenType(contract, bridgeAddress) + + // Then + expect(type).to.equal(ERC_TYPES.ERC677) + }) + + it('should return ERC677 if isBridge returns true and bridgeContract not present', async () => { + // Given + const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26' + const contract = { + methods: { + isBridge: () => { + return { + call: () => Promise.resolve(true) + } + } + } + } + + // When + const type = await getTokenType(contract, bridgeAddress) + + // Then + expect(type).to.equal(ERC_TYPES.ERC677) + }) + + it('should return ERC20 if isBridge returns false', async () => { + // Given + const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26' + const contract = { + methods: { + bridgeContract: () => { + return { + call: () => Promise.reject() + } + }, + isBridge: () => { + return { + call: () => Promise.resolve(false) + } + } + } + } + + // When + const type = await getTokenType(contract, bridgeAddress) + + // Then + expect(type).to.equal(ERC_TYPES.ERC20) + }) }) diff --git a/commons/utils.js b/commons/utils.js index e8779816..965eb008 100644 --- a/commons/utils.js +++ b/commons/utils.js @@ -12,6 +12,10 @@ function decodeBridgeMode(bridgeModeHash) { return BRIDGE_MODES.ERC_TO_NATIVE case '0x2544fbb9': return BRIDGE_MODES.ARBITRARY_MESSAGE + case '0x16ea01e9': + return BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC + case '0x76595b56': + return BRIDGE_MODES.AMB_ERC_TO_ERC default: throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`) } @@ -46,10 +50,31 @@ const getTokenType = async (bridgeTokenContract, bridgeAddress) => { return ERC_TYPES.ERC20 } } catch (e) { - return ERC_TYPES.ERC20 + try { + const isBridge = await bridgeTokenContract.methods.isBridge(bridgeAddress).call() + if (isBridge) { + return ERC_TYPES.ERC677 + } else { + return ERC_TYPES.ERC20 + } + } catch (e) { + return ERC_TYPES.ERC20 + } } } +const isErcToErcMode = bridgeMode => { + return ( + bridgeMode === BRIDGE_MODES.ERC_TO_ERC || + bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC || + bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC + ) +} + +const isMediatorMode = bridgeMode => { + return bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC || bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC +} + const getUnit = bridgeMode => { let unitHome = null let unitForeign = null @@ -62,6 +87,9 @@ const getUnit = bridgeMode => { } else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { unitHome = 'Native coins' unitForeign = 'Tokens' + } else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) { + unitHome = 'Tokens' + unitForeign = 'Tokens' } else { throw new Error(`Unrecognized bridge mode: ${bridgeMode}`) } @@ -260,5 +288,7 @@ module.exports = { normalizeGasPrice, gasPriceFromSupplier, gasPriceFromContract, - gasPriceWithinLimits + gasPriceWithinLimits, + isErcToErcMode, + isMediatorMode } diff --git a/contracts b/contracts index a7ce4441..e550067c 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit a7ce4441ab77e1c3e4d01017d862c53516933645 +Subproject commit e550067c7680d416f40de95fc4c8e5b073279ee6 diff --git a/ui/src/components/Bridge.js b/ui/src/components/Bridge.js index f3d95ca4..bc9bd1bd 100644 --- a/ui/src/components/Bridge.js +++ b/ui/src/components/Bridge.js @@ -4,7 +4,7 @@ import { toHex } from 'web3-utils' import foreignLogoPurple from '../assets/images/logos/logo-poa-20-purple@2x.png' import homeLogoPurple from '../assets/images/logos/logo-poa-sokol-purple@2x.png' import swal from 'sweetalert' -import { BRIDGE_MODES, ERC_TYPES } from '../../../commons' +import { BRIDGE_MODES, ERC_TYPES, isErcToErcMode } from '../../../commons' import { BridgeAddress } from './index' import { BridgeForm } from './index' import { BridgeNetwork } from './index' @@ -63,7 +63,6 @@ export class Bridge extends React.Component { async _sendToHome(amount) { const { web3Store, homeStore, alertStore, txStore, bridgeMode } = this.props.RootStore - const isErcToErcMode = bridgeMode === BRIDGE_MODES.ERC_TO_ERC const { isLessThan, isGreaterThan } = this if (web3Store.metamaskNet.id.toString() !== web3Store.homeNet.id.toString()) { swal('Error', `Please switch wallet to ${web3Store.homeNet.name} network`, 'error') @@ -98,7 +97,7 @@ export class Bridge extends React.Component { } else { try { alertStore.setLoading(true) - if (isErcToErcMode) { + if (isErcToErcMode(bridgeMode)) { return txStore.erc677transferAndCall({ to: homeStore.COMMON_HOME_BRIDGE_ADDRESS, from: web3Store.defaultAccount.address, @@ -259,7 +258,6 @@ export class Bridge extends React.Component { loadHomeDetails = () => { const { web3Store, homeStore, bridgeMode } = this.props.RootStore - const isErcToErcMode = bridgeMode === BRIDGE_MODES.ERC_TO_ERC const isExternalErc20 = bridgeMode === BRIDGE_MODES.ERC_TO_ERC || bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE const modalData = { @@ -274,7 +272,7 @@ export class Bridge extends React.Component { minPerTx: homeStore.minPerTx, totalBalance: homeStore.balance, balance: homeStore.getDisplayedBalance(), - displayTokenAddress: isErcToErcMode, + displayTokenAddress: isErcToErcMode(bridgeMode), tokenAddress: homeStore.tokenAddress, tokenName: homeStore.tokenName, displayBridgeLimits: true, diff --git a/ui/src/components/BridgeStatistics.js b/ui/src/components/BridgeStatistics.js index 61adbf0e..fade0a1b 100644 --- a/ui/src/components/BridgeStatistics.js +++ b/ui/src/components/BridgeStatistics.js @@ -9,7 +9,8 @@ export const BridgeStatistics = ({ homeNativeSupplyTitle, foreignSupply, homeSymbol, - foreignSymbol + foreignSymbol, + displayNetworkTokenSupply }) => (
@@ -21,7 +22,13 @@ export const BridgeStatistics = ({ />
diff --git a/ui/src/components/FeeStatistics.js b/ui/src/components/FeeStatistics.js index 9043045a..a845acde 100644 --- a/ui/src/components/FeeStatistics.js +++ b/ui/src/components/FeeStatistics.js @@ -10,7 +10,7 @@ export const FeeStatistics = ({ depositFeeCollected, withdrawFeeCollected }) =>
{depositFeeCollected.shouldDisplay && ( {depositFeeCollected.shouldDisplay && withdrawFeeCollected.shouldDisplay &&
} {withdrawFeeCollected.shouldDisplay && ( { const networkTitle = isHome ? 'Bridge Home' : 'Bridge Foreign' const logoClass = isHome ? 'home-logo home-logo-modal' : 'foreign-logo foreign-logo-modal' - const totalTitle = isHome - ? nativeSupplyTitle - ? `Native Coins Amount` - : `Totally minted by the bridge` - : `${currency} Tokens Amount` + const totalTitle = + isHome && !displayTokenAddress + ? nativeSupplyTitle + ? `Native Coins Amount` + : `Totally minted by the bridge` + : `${currency} Tokens Amount` const totalAmount = isHome ? totalBalance : totalSupply const formattedBalance = isNaN(numeral(balance).format('0.00', Math.floor)) ? numeral(0).format('0,0.00', Math.floor) diff --git a/ui/src/components/StatisticsPage.js b/ui/src/components/StatisticsPage.js index cd9f3c7c..9013df51 100644 --- a/ui/src/components/StatisticsPage.js +++ b/ui/src/components/StatisticsPage.js @@ -1,6 +1,6 @@ import React from 'react' import yn from './utils/yn' -import { BRIDGE_MODES } from '../../../commons' +import { BRIDGE_MODES, isErcToErcMode } from '../../../commons' import { BridgeStatistics } from './index' import { Redirect } from 'react-router' import { TransactionsStatistics } from './TransactionsStatistics' @@ -12,9 +12,10 @@ import { FeeStatistics } from './FeeStatistics' export class StatisticsPage extends React.Component { render() { const { homeStore, foreignStore, bridgeMode, web3Store } = this.props.RootStore + const statisticsReady = homeStore.statistics.finished && foreignStore.statistics.finished const isNativeToErc = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC - const leftTitle = isNativeToErc ? 'Deposits' : 'Withdraws' - const rightTitle = isNativeToErc ? 'Withdraws' : 'Deposits' + const leftTitle = isNativeToErc ? 'Deposits' : 'Withdrawals' + const rightTitle = isNativeToErc ? 'Withdrawals' : 'Deposits' const { REACT_APP_UI_HOME_WITHOUT_EVENTS: HOME, REACT_APP_UI_FOREIGN_WITHOUT_EVENTS: FOREIGN } = process.env const withoutEvents = web3Store.metamaskNet.id === web3Store.homeNet.id.toString() ? yn(HOME) : yn(FOREIGN) @@ -27,13 +28,14 @@ export class StatisticsPage extends React.Component {
Bridge Statistics
{homeStore.depositFeeCollected.finished && @@ -48,17 +50,17 @@ export class StatisticsPage extends React.Component {
Tokens {leftTitle}
Tokens {rightTitle}
diff --git a/ui/src/stores/ForeignStore.js b/ui/src/stores/ForeignStore.js index 7ec408c6..b7cfc415 100644 --- a/ui/src/stores/ForeignStore.js +++ b/ui/src/stores/ForeignStore.js @@ -10,7 +10,9 @@ import { getBridgeABIs, getTokenType, ERC20_BYTES32_ABI, - getDeployedAtBlock + getDeployedAtBlock, + isMediatorMode, + HOME_V1_ABI } from '../../../commons' import { getMaxPerTxLimit, @@ -103,6 +105,11 @@ class ForeignStore { @observable tokenType = '' + @observable + statistics = { + finished: false + } + feeManager = { totalFeeDistributedFromSignatures: BN(0), totalFeeDistributedFromAffirmation: BN(0) @@ -142,6 +149,7 @@ class ForeignStore { this.getCurrentLimit() this.getFee() this.getValidators() + this.getStatistics() this.getFeeEvents() setInterval(() => { this.getBlockNumber() @@ -239,58 +247,62 @@ class ForeignStore { @action async getEvents(fromBlock, toBlock) { - try { - fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 - toBlock = toBlock || this.filteredBlockNumber || 'latest' + fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 + toBlock = toBlock || this.filteredBlockNumber || 'latest' - if (fromBlock < 0) { - fromBlock = 0 - } + if (fromBlock < 0) { + fromBlock = 0 + } - let foreignEvents = await getPastEvents(this.foreignBridge, fromBlock, toBlock).catch(e => { - console.error("Couldn't get events", e) - return [] - }) - - if (!this.filter) { - this.events = foreignEvents - } - - if (this.waitingForConfirmation.size) { - const confirmationEvents = foreignEvents.filter( - event => - event.event === 'RelayedMessage' && this.waitingForConfirmation.has(event.returnValues.transactionHash) - ) - confirmationEvents.forEach(async event => { - const TxReceipt = await this.getTxReceipt(event.transactionHash) - if (TxReceipt && TxReceipt.logs && TxReceipt.logs.length > 1 && this.waitingForConfirmation.size) { - this.alertStore.setLoadingStepIndex(3) - const urlExplorer = this.getExplorerTxUrl(event.transactionHash) - const unitReceived = getUnit(this.rootStore.bridgeMode).unitForeign - setTimeout(() => { - this.alertStore.pushSuccess( - `${unitReceived} received on ${this.networkName} on Tx - - ${event.transactionHash}`, - this.alertStore.FOREIGN_TRANSFER_SUCCESS - ) - }, 2000) - this.waitingForConfirmation.delete(event.returnValues.transactionHash) - } + if (!isMediatorMode(this.rootStore.bridgeMode)) { + try { + let foreignEvents = await getPastEvents(this.foreignBridge, fromBlock, toBlock).catch(e => { + console.error("Couldn't get events", e) + return [] }) - if (confirmationEvents.length) { - removePendingTransaction() + if (!this.filter) { + this.events = foreignEvents } - } - return foreignEvents - } catch (e) { - this.alertStore.pushError( - `Cannot establish connection to Foreign Network.\n + if (this.waitingForConfirmation.size) { + const confirmationEvents = foreignEvents.filter( + event => + event.event === 'RelayedMessage' && this.waitingForConfirmation.has(event.returnValues.transactionHash) + ) + confirmationEvents.forEach(async event => { + const TxReceipt = await this.getTxReceipt(event.transactionHash) + if (TxReceipt && TxReceipt.logs && TxReceipt.logs.length > 1 && this.waitingForConfirmation.size) { + this.alertStore.setLoadingStepIndex(3) + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitForeign + setTimeout(() => { + this.alertStore.pushSuccess( + `${unitReceived} received on ${this.networkName} on Tx + + ${event.transactionHash}`, + this.alertStore.FOREIGN_TRANSFER_SUCCESS + ) + }, 2000) + this.waitingForConfirmation.delete(event.returnValues.transactionHash) + } + }) + + if (confirmationEvents.length) { + removePendingTransaction() + } + } + + return foreignEvents + } catch (e) { + this.alertStore.pushError( + `Cannot establish connection to Foreign Network.\n Please make sure you have set it up in env variables`, - this.alertStore.FOREIGN_CONNECTION_ERROR - ) + this.alertStore.FOREIGN_CONNECTION_ERROR + ) + } + } else { + this.detectMediatorTransferFinished(fromBlock, toBlock) } } @@ -379,30 +391,68 @@ class ForeignStore { @action async getValidators() { - try { - const foreignValidatorsAddress = await getValidatorContract(this.foreignBridge) - this.foreignBridgeValidators = new this.foreignWeb3.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress) + if (!isMediatorMode(this.rootStore.bridgeMode)) { + try { + const foreignValidatorsAddress = await getValidatorContract(this.foreignBridge) + this.foreignBridgeValidators = new this.foreignWeb3.eth.Contract( + BRIDGE_VALIDATORS_ABI, + foreignValidatorsAddress + ) - this.requiredSignatures = await getRequiredSignatures(this.foreignBridgeValidators) - this.validatorsCount = await getValidatorCount(this.foreignBridgeValidators) + this.requiredSignatures = await getRequiredSignatures(this.foreignBridgeValidators) + this.validatorsCount = await getValidatorCount(this.foreignBridgeValidators) - this.validators = await getValidatorList(foreignValidatorsAddress, this.foreignWeb3.eth) - } catch (e) { - console.error(e) + this.validators = await getValidatorList(foreignValidatorsAddress, this.foreignWeb3.eth) + } catch (e) { + console.error(e) + } } } - async getFeeEvents() { + async getStatistics() { try { - const deployedAtBlock = await getDeployedAtBlock(this.foreignBridge) - const events = await getPastEvents(this.foreignBridge, deployedAtBlock, 'latest') - - processLargeArrayAsync(events, this.processEvent, () => { - this.feeEventsFinished = true - }) + if (isMediatorMode(this.rootStore.bridgeMode)) { + const events = await getPastEvents(this.foreignBridge, 0, 'latest', 'TokensBridged') + processLargeArrayAsync(events, this.processMediatorEvent, () => { + this.statistics.finished = true + this.rootStore.homeStore.statistics.totalBridged = this.rootStore.homeStore.statistics.totalBridged.plus( + this.rootStore.homeStore.statistics.withdrawalsValue + ) + }) + } else { + this.statistics.finished = true + } } catch (e) { console.error(e) - this.getFeeEvents() + this.getStatistics() + } + } + + processMediatorEvent = event => { + if (event.returnValues && event.returnValues.recipient) { + this.rootStore.homeStore.statistics.users.add(event.returnValues.recipient) + } + this.rootStore.homeStore.statistics.withdrawals++ + this.rootStore.homeStore.statistics.withdrawalsValue = this.rootStore.homeStore.statistics.withdrawalsValue.plus( + BN(fromDecimals(event.returnValues.value, this.tokenDecimals)) + ) + } + + async getFeeEvents() { + if (!isMediatorMode(this.rootStore.bridgeMode)) { + try { + const deployedAtBlock = await getDeployedAtBlock(this.foreignBridge) + const events = await getPastEvents(this.foreignBridge, deployedAtBlock, 'latest') + + processLargeArrayAsync(events, this.processEvent, () => { + this.feeEventsFinished = true + }) + } catch (e) { + console.error(e) + this.getFeeEvents() + } + } else { + this.feeEventsFinished = true } } @@ -417,6 +467,33 @@ class ForeignStore { ) } } + + async detectMediatorTransferFinished(fromBlock, toBlock) { + if (this.waitingForConfirmation.size > 0) { + try { + const events = await getPastEvents(this.foreignBridge, fromBlock, toBlock, 'TokensBridged') + const confirmationEvents = events.filter(event => this.waitingForConfirmation.has(event.returnValues.messageId)) + if (confirmationEvents.length > 0) { + const event = confirmationEvents[0] + this.alertStore.setLoadingStepIndex(3) + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitForeign + this.waitingForConfirmation.delete(event.returnValues.messageId) + removePendingTransaction() + setTimeout(() => { + this.alertStore.pushSuccess( + `${unitReceived} received on ${this.networkName} on Tx + + ${event.transactionHash}`, + this.alertStore.FOREIGN_TRANSFER_SUCCESS + ) + }, 2000) + } + } catch (e) { + console.log(e) + } + } + } } export default ForeignStore diff --git a/ui/src/stores/HomeStore.js b/ui/src/stores/HomeStore.js index 5f0bdd84..27574002 100644 --- a/ui/src/stores/HomeStore.js +++ b/ui/src/stores/HomeStore.js @@ -12,7 +12,9 @@ import { getBridgeABIs, HOME_V1_ABI, ERC20_BYTES32_ABI, - getDeployedAtBlock + getDeployedAtBlock, + isErcToErcMode, + isMediatorMode } from '../../../commons' import { getMaxPerTxLimit, @@ -37,7 +39,8 @@ import { getBlockRewardContract, getValidatorContract, getRequiredSignatures, - getValidatorCount + getValidatorCount, + getFee } from './utils/contract' import { balanceLoaded, removePendingTransaction } from './utils/testUtils' import sleep from './utils/sleep' @@ -113,19 +116,23 @@ class HomeStore { statistics = { deposits: 0, depositsValue: BN(0), - withdraws: 0, - withdrawsValue: BN(0), + withdrawals: 0, + withdrawalsValue: BN(0), totalBridged: BN(0), users: new Set(), finished: false } + @observable + feeEventsFinished = false + @observable depositFeeCollected = { value: BN(0), type: '', shouldDisplay: false, - finished: false + finished: false, + title: '' } @observable @@ -133,7 +140,8 @@ class HomeStore { value: BN(0), type: '', shouldDisplay: false, - finished: false + finished: false, + title: '' } feeManager = { @@ -166,7 +174,7 @@ class HomeStore { } const { HOME_ABI } = getBridgeABIs(this.rootStore.bridgeMode) this.homeBridge = new this.homeWeb3.eth.Contract(HOME_ABI, this.COMMON_HOME_BRIDGE_ADDRESS) - if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { + if (isErcToErcMode(this.rootStore.bridgeMode)) { await this.getTokenInfo() } else if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { await this.getBlockRewardContract() @@ -180,6 +188,7 @@ class HomeStore { this.getFee() this.getValidators() this.getStatistics() + this.getFeeEvents() this.calculateCollectedFees() setInterval(() => { this.getEvents() @@ -194,8 +203,6 @@ class HomeStore { try { this.tokenAddress = await getErc677TokenAddress(this.homeBridge) this.tokenContract = new this.homeWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, this.tokenAddress) - this.symbol = await getSymbol(this.tokenContract) - this.tokenName = await getName(this.tokenContract) const alternativeContract = new this.homeWeb3.eth.Contract(ERC20_BYTES32_ABI, this.tokenAddress) try { this.symbol = await getSymbol(this.tokenContract) @@ -243,7 +250,7 @@ class HomeStore { @action async getBalance() { try { - if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { + if (isErcToErcMode(this.rootStore.bridgeMode)) { this.balance = await getTotalSupply(this.tokenContract) this.web3Store.getWeb3Promise.then(async () => { this.userBalance = await getBalanceOf(this.tokenContract, this.web3Store.defaultAccount.address) @@ -264,18 +271,28 @@ class HomeStore { @action async getFee() { - const feeManager = await getFeeManager(this.homeBridge) - if (feeManager !== ZERO_ADDRESS) { - const feeManagerModeHash = await getFeeManagerMode(this.homeBridge) - this.feeManager.feeManagerMode = decodeFeeManagerMode(feeManagerModeHash) + if (!isMediatorMode(this.rootStore.bridgeMode)) { + const feeManager = await getFeeManager(this.homeBridge) + if (feeManager !== ZERO_ADDRESS) { + const feeManagerModeHash = await getFeeManagerMode(this.homeBridge) + this.feeManager.feeManagerMode = decodeFeeManagerMode(feeManagerModeHash) - if (this.feeManager.feeManagerMode === FEE_MANAGER_MODE.BOTH_DIRECTIONS) { - this.feeManager.homeFee = await getHomeFee(this.homeBridge) - this.feeManager.foreignFee = await getForeignFee(this.homeBridge) + if (this.feeManager.feeManagerMode === FEE_MANAGER_MODE.BOTH_DIRECTIONS) { + this.feeManager.homeFee = await getHomeFee(this.homeBridge) + this.feeManager.foreignFee = await getForeignFee(this.homeBridge) + } else { + this.feeManager.homeFee = new BN(0) + this.feeManager.foreignFee = await getForeignFee(this.homeBridge) + } } else { + this.feeManager.feeManagerMode = FEE_MANAGER_MODE.UNDEFINED this.feeManager.homeFee = new BN(0) - this.feeManager.foreignFee = await getForeignFee(this.homeBridge) + this.feeManager.foreignFee = new BN(0) } + } else if (this.rootStore.bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) { + this.feeManager.feeManagerMode = FEE_MANAGER_MODE.ONE_DIRECTION_STAKE + this.feeManager.homeFee = await getFee(this.homeBridge) + this.feeManager.foreignFee = new BN(0) } else { this.feeManager.feeManagerMode = FEE_MANAGER_MODE.UNDEFINED this.feeManager.homeFee = new BN(0) @@ -285,64 +302,68 @@ class HomeStore { @action async getEvents(fromBlock, toBlock) { - try { - fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 - toBlock = toBlock || this.filteredBlockNumber || 'latest' + fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 + toBlock = toBlock || this.filteredBlockNumber || 'latest' - if (fromBlock < 0) { - fromBlock = 0 - } + if (fromBlock < 0) { + fromBlock = 0 + } - let events = await getPastEvents(this.homeBridge, fromBlock, toBlock).catch(e => { - console.error("Couldn't get events", e) - return [] - }) - - let homeEvents = [] - await asyncForEach(events, async event => { - if (event.event === 'SignedForUserRequest' || event.event === 'CollectedSignatures') { - event.signedTxHash = await this.getSignedTx(event.returnValues.messageHash) - } - homeEvents.push(event) - }) - - if (!this.filter) { - this.events = homeEvents - } - - if (this.waitingForConfirmation.size) { - const confirmationEvents = homeEvents.filter( - event => - event.event === 'AffirmationCompleted' && - this.waitingForConfirmation.has(event.returnValues.transactionHash) - ) - confirmationEvents.forEach(event => { - this.alertStore.setLoadingStepIndex(3) - const urlExplorer = this.getExplorerTxUrl(event.transactionHash) - const unitReceived = getUnit(this.rootStore.bridgeMode).unitHome - setTimeout(() => { - this.alertStore.pushSuccess( - `${unitReceived} received on ${this.networkName} on Tx - - ${event.transactionHash}`, - this.alertStore.HOME_TRANSFER_SUCCESS - ) - }, 2000) - this.waitingForConfirmation.delete(event.returnValues.transactionHash) + if (!isMediatorMode(this.rootStore.bridgeMode)) { + try { + let events = await getPastEvents(this.homeBridge, fromBlock, toBlock).catch(e => { + console.error("Couldn't get events", e) + return [] }) - if (confirmationEvents.length) { - removePendingTransaction() - } - } + let homeEvents = [] + await asyncForEach(events, async event => { + if (event.event === 'SignedForUserRequest' || event.event === 'CollectedSignatures') { + event.signedTxHash = await this.getSignedTx(event.returnValues.messageHash) + } + homeEvents.push(event) + }) - return homeEvents - } catch (e) { - this.alertStore.pushError( - `Cannot establish connection to Home Network.\n + if (!this.filter) { + this.events = homeEvents + } + + if (this.waitingForConfirmation.size) { + const confirmationEvents = homeEvents.filter( + event => + event.event === 'AffirmationCompleted' && + this.waitingForConfirmation.has(event.returnValues.transactionHash) + ) + confirmationEvents.forEach(event => { + this.alertStore.setLoadingStepIndex(3) + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitHome + setTimeout(() => { + this.alertStore.pushSuccess( + `${unitReceived} received on ${this.networkName} on Tx + + ${event.transactionHash}`, + this.alertStore.HOME_TRANSFER_SUCCESS + ) + }, 2000) + this.waitingForConfirmation.delete(event.returnValues.transactionHash) + }) + + if (confirmationEvents.length) { + removePendingTransaction() + } + } + + return homeEvents + } catch (e) { + this.alertStore.pushError( + `Cannot establish connection to Home Network.\n Please make sure you have set it up in env variables`, - this.alertStore.HOME_CONNECTION_ERROR - ) + this.alertStore.HOME_CONNECTION_ERROR + ) + } + } else { + this.detectMediatorTransferFinished(fromBlock, toBlock) } } @@ -408,30 +429,40 @@ class HomeStore { @action async getValidators() { - try { - const homeValidatorsAddress = await getValidatorContract(this.homeBridge) - this.homeBridgeValidators = new this.homeWeb3.eth.Contract(BRIDGE_VALIDATORS_ABI, homeValidatorsAddress) + if (!isMediatorMode(this.rootStore.bridgeMode)) { + try { + const homeValidatorsAddress = await getValidatorContract(this.homeBridge) + this.homeBridgeValidators = new this.homeWeb3.eth.Contract(BRIDGE_VALIDATORS_ABI, homeValidatorsAddress) - this.requiredSignatures = await getRequiredSignatures(this.homeBridgeValidators) - this.validatorsCount = await getValidatorCount(this.homeBridgeValidators) + this.requiredSignatures = await getRequiredSignatures(this.homeBridgeValidators) + this.validatorsCount = await getValidatorCount(this.homeBridgeValidators) - this.validators = await getValidatorList(homeValidatorsAddress, this.homeWeb3.eth) - } catch (e) { - console.error(e) + this.validators = await getValidatorList(homeValidatorsAddress, this.homeWeb3.eth) + } catch (e) { + console.error(e) + } } } async getStatistics() { try { - const deployedAtBlock = await getDeployedAtBlock(this.homeBridge) - const { HOME_ABI } = getBridgeABIs(this.rootStore.bridgeMode) - const abi = [...HOME_V1_ABI, ...HOME_ABI] - const contract = new this.homeWeb3.eth.Contract(abi, this.COMMON_HOME_BRIDGE_ADDRESS) - const events = await getPastEvents(contract, deployedAtBlock, 'latest') - processLargeArrayAsync(events, this.processEvent, () => { - this.statistics.finished = true - this.statistics.totalBridged = this.statistics.depositsValue.plus(this.statistics.withdrawsValue) - }) + if (!isMediatorMode(this.rootStore.bridgeMode)) { + const deployedAtBlock = await getDeployedAtBlock(this.homeBridge) + const { HOME_ABI } = getBridgeABIs(this.rootStore.bridgeMode) + const abi = [...HOME_V1_ABI, ...HOME_ABI] + const contract = new this.homeWeb3.eth.Contract(abi, this.COMMON_HOME_BRIDGE_ADDRESS) + const events = await getPastEvents(contract, deployedAtBlock, 'latest') + processLargeArrayAsync(events, this.processEvent, () => { + this.statistics.finished = true + this.statistics.totalBridged = this.statistics.totalBridged.plus(this.statistics.depositsValue) + }) + } else { + const events = await getPastEvents(this.homeBridge, 0, 'latest', 'TokensBridged') + processLargeArrayAsync(events, this.processMediatorEvent, () => { + this.statistics.finished = true + this.statistics.totalBridged = this.statistics.depositsValue.plus(this.statistics.withdrawalsValue) + }) + } } catch (e) { console.error(e) this.getStatistics() @@ -448,8 +479,8 @@ class HomeStore { BN(fromDecimals(event.returnValues.value, this.tokenDecimals)) ) } else if (event.event === 'AffirmationCompleted' || event.event === 'Withdraw') { - this.statistics.withdraws++ - this.statistics.withdrawsValue = this.statistics.withdrawsValue.plus( + this.statistics.withdrawals++ + this.statistics.withdrawalsValue = this.statistics.withdrawalsValue.plus( BN(fromDecimals(event.returnValues.value, this.tokenDecimals)) ) } else if (event.event === 'FeeDistributedFromSignatures') { @@ -463,9 +494,49 @@ class HomeStore { } } + processMediatorEvent = event => { + if (event.returnValues && event.returnValues.recipient) { + this.statistics.users.add(event.returnValues.recipient) + } + this.statistics.deposits++ + this.statistics.depositsValue = this.statistics.depositsValue.plus( + BN(fromDecimals(event.returnValues.value, this.tokenDecimals)) + ) + } + + async getFeeEvents() { + try { + if (this.rootStore.bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) { + const blockRewardAddress = await getBlockRewardContract(this.homeBridge) + const blockRewardContract = new this.homeWeb3.eth.Contract(BLOCK_REWARD_ABI, blockRewardAddress) + const events = await getPastEvents(blockRewardContract, 0, 'latest', 'BridgeTokenRewardAdded', { + filter: { + bridge: this.COMMON_HOME_BRIDGE_ADDRESS + } + }) + + processLargeArrayAsync(events, this.processFeeEvents, () => { + this.feeEventsFinished = true + }) + } else { + this.feeEventsFinished = true + } + } catch (e) { + console.error(e) + this.getFeeEvents() + } + } + + processFeeEvents = event => { + this.feeManager.totalFeeDistributedFromSignatures = this.feeManager.totalFeeDistributedFromSignatures.plus( + BN(fromDecimals(event.returnValues.amount, this.tokenDecimals)) + ) + } + calculateCollectedFees() { if ( !this.statistics.finished || + !this.feeEventsFinished || !this.rootStore.foreignStore.feeEventsFinished || !this.feeManager.feeManagerMode || !this.rootStore.foreignStore.feeManager.feeManagerMode @@ -493,6 +564,10 @@ class HomeStore { ? this.feeManager.totalFeeDistributedFromAffirmation : this.rootStore.foreignStore.feeManager.totalFeeDistributedFromAffirmation + if (this.rootStore.bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) { + this.depositFeeCollected.title = 'Withdrawal Fees' + } + this.depositFeeCollected.finished = true this.withdrawFeeCollected.finished = true } @@ -502,9 +577,7 @@ class HomeStore { } getDisplayedBalance() { - return this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC - ? this.userBalance - : this.web3Store.defaultAccount.homeBalance + return isErcToErcMode(this.rootStore.bridgeMode) ? this.userBalance : this.web3Store.defaultAccount.homeBalance } async getBlockRewardContract() { @@ -527,6 +600,33 @@ class HomeStore { return sleep(5000).then(() => this.waitUntilProcessed(txHash, value)) } } + + async detectMediatorTransferFinished(fromBlock, toBlock) { + if (this.waitingForConfirmation.size > 0) { + try { + const events = await getPastEvents(this.homeBridge, fromBlock, toBlock, 'TokensBridged') + const confirmationEvents = events.filter(event => this.waitingForConfirmation.has(event.returnValues.messageId)) + if (confirmationEvents.length > 0) { + const event = events[0] + this.alertStore.setLoadingStepIndex(3) + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitHome + this.waitingForConfirmation.delete(event.returnValues.messageId) + removePendingTransaction() + setTimeout(() => { + this.alertStore.pushSuccess( + `${unitReceived} received on ${this.networkName} on Tx + + ${event.transactionHash}`, + this.alertStore.HOME_TRANSFER_SUCCESS + ) + }, 2000) + } + } catch (e) { + console.log(e) + } + } + } } export default HomeStore diff --git a/ui/src/stores/utils/contract.js b/ui/src/stores/utils/contract.js index 2814e9db..c47878ce 100644 --- a/ui/src/stores/utils/contract.js +++ b/ui/src/stores/utils/contract.js @@ -27,8 +27,8 @@ export const getCurrentLimit = async (contract, decimals) => { } } -export const getPastEvents = (contract, fromBlock, toBlock, event = 'allEvents') => - commonGetPastEvents(contract, { fromBlock, toBlock, event }) +export const getPastEvents = (contract, fromBlock, toBlock, event = 'allEvents', options = {}) => + commonGetPastEvents(contract, { fromBlock, toBlock, event, options }) export const getErc677TokenAddress = contract => contract.methods.erc677token().call() @@ -86,6 +86,15 @@ export const getForeignFee = async contract => { return new BN(fromWei(feeInWei.toString())) } +export const getFee = async contract => { + try { + const feeInWei = await contract.methods.getFee().call() + return new BN(fromWei(feeInWei.toString())) + } catch (e) { + return new BN(0) + } +} + export const getBlockRewardContract = contract => contract.methods.blockRewardContract().call() export const getValidatorContract = contract => contract.methods.validatorContract().call() diff --git a/ui/src/stores/utils/rewardable.js b/ui/src/stores/utils/rewardable.js index 98f11fd9..f6fc6836 100644 --- a/ui/src/stores/utils/rewardable.js +++ b/ui/src/stores/utils/rewardable.js @@ -65,6 +65,15 @@ export const getRewardableData = (homeFeeManager, foreignFeeManager) => { displayDeposit: true, displayWithdraw: false } + } else if (homeFeeManager.feeManagerMode === FEE_MANAGER_MODE.ONE_DIRECTION_STAKE) { + return { + homeFee: homeFeeManager.homeFee, + foreignFee: new BN(0), + depositSymbol: 'home', + withdrawSymbol: '', + displayDeposit: true, + displayWithdraw: false + } } else { return { homeFee: new BN(0),