Fix handling of Compound related Transfer events (#595)
This commit is contained in:
parent
2e6179f974
commit
5cedacafe5
@ -38,7 +38,7 @@
|
||||
"ercToNativeBridge": {
|
||||
"home": "0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c",
|
||||
"foreign": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
|
||||
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
|
||||
"foreignToken": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
||||
"monitor": "http://monitor-erc20-native:3012/bridge"
|
||||
},
|
||||
"amb": {
|
||||
|
@ -32,7 +32,7 @@ FOREIGN_GAS_PRICE=10000000000
|
||||
FOREIGN_REWARDABLE=false
|
||||
|
||||
BLOCK_REWARD_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B
|
||||
ERC20_TOKEN_ADDRESS=0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9
|
||||
ERC20_TOKEN_ADDRESS=0x6B175474E89094C44Da98b954EedeAC495271d0F
|
||||
|
||||
REQUIRED_NUMBER_OF_VALIDATORS=1
|
||||
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"
|
||||
|
@ -46,24 +46,6 @@ async function main(bridgeMode, eventsInfo) {
|
||||
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
const erc20Address = await foreignBridge.methods.erc20token().call()
|
||||
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
|
||||
let investedAmountInDai = 0
|
||||
let bridgeDsrBalance = 0
|
||||
let displayChaiToken = false
|
||||
|
||||
try {
|
||||
logger.debug('calling foreignBridge.methods.isChaiTokenEnabled')
|
||||
if (await foreignBridge.methods.isChaiTokenEnabled().call()) {
|
||||
displayChaiToken = true
|
||||
logger.debug('calling foreignBridge.methods.investedAmountInDai')
|
||||
investedAmountInDai = await foreignBridge.methods.investedAmountInDai().call()
|
||||
logger.debug('calling foreignBridge.methods.dsrBalance')
|
||||
bridgeDsrBalance = await foreignBridge.methods.dsrBalance().call()
|
||||
} else {
|
||||
logger.debug('Chai token is currently disabled')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug('Methods for chai token are not present')
|
||||
}
|
||||
|
||||
logger.debug('calling erc20Contract.methods.balanceOf')
|
||||
const foreignErc20Balance = await erc20Contract.methods
|
||||
@ -85,29 +67,16 @@ async function main(bridgeMode, eventsInfo) {
|
||||
const burntCoinsBN = new BN(burntCoins)
|
||||
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
|
||||
const foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
|
||||
const investedAmountInDaiBN = new BN(investedAmountInDai)
|
||||
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)
|
||||
|
||||
const diff = foreignErc20BalanceBN
|
||||
.plus(investedAmountInDaiBN)
|
||||
.minus(totalSupplyBN)
|
||||
.toFixed()
|
||||
|
||||
const foreign = {
|
||||
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
|
||||
}
|
||||
|
||||
if (displayChaiToken) {
|
||||
foreign.investedErc20Balance = Web3Utils.fromWei(investedAmountInDai)
|
||||
foreign.accumulatedInterest = Web3Utils.fromWei(bridgeDsrBalanceBN.minus(investedAmountInDaiBN).toString(10))
|
||||
}
|
||||
|
||||
const diff = foreignErc20BalanceBN.minus(totalSupplyBN).toFixed()
|
||||
logger.debug('Done')
|
||||
return {
|
||||
home: {
|
||||
totalSupply: Web3Utils.fromWei(totalSupplyBN.toFixed())
|
||||
},
|
||||
foreign,
|
||||
foreign: {
|
||||
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
|
||||
},
|
||||
balanceDiff: Number(Web3Utils.fromWei(diff)),
|
||||
...blockRanges,
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
|
@ -11,7 +11,6 @@ const {
|
||||
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI
|
||||
} = require('../../commons')
|
||||
const { normalizeEventInformation } = require('./message')
|
||||
const { filterTransferBeforeES } = require('./tokenUtils')
|
||||
const { writeFile, readCacheFile } = require('./file')
|
||||
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./web3')
|
||||
const { getPastEvents } = require('./web3Cache')
|
||||
@ -160,80 +159,32 @@ async function main(mode) {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
},
|
||||
chain: 'foreign'
|
||||
}))
|
||||
.map(normalizeEvent)
|
||||
.filter(e => e.recipient !== ZERO_ADDRESS) // filter mint operation during SCD-to-MCD swaps
|
||||
.filter(e => e.recipient.toLowerCase() !== '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643') // filter cDai withdraws during compounding
|
||||
|
||||
// Get transfer events for each previously used Sai token
|
||||
const saiTokenAddress = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'
|
||||
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, saiTokenAddress)
|
||||
logger.debug('Half duplex token:', saiTokenAddress)
|
||||
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
|
||||
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
|
||||
const blockNumberHalfDuplexDisabled = 9884448
|
||||
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: Math.min(blockNumberHalfDuplexDisabled, foreignDelayedBlockNumber),
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
},
|
||||
chain: 'foreign'
|
||||
})).map(normalizeEvent)
|
||||
|
||||
let directTransfers = transferEvents
|
||||
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
|
||||
if (tokensSwappedAbiExists) {
|
||||
logger.debug('collecting half duplex tokens participated in the bridge balance')
|
||||
logger.debug("calling foreignBridge.getPastEvents('TokensSwapped')")
|
||||
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
|
||||
event: 'TokensSwapped',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignBlockNumber,
|
||||
chain: 'foreign',
|
||||
safeToBlock: foreignDelayedBlockNumber
|
||||
})
|
||||
|
||||
// Get token swap events emitted by foreign bridge
|
||||
const bridgeTokensSwappedEvents = tokensSwappedEvents.filter(e => e.address === COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
|
||||
// Get transfer events for each previous erc20
|
||||
const uniqueTokenAddressesSet = new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))
|
||||
|
||||
// Exclude chai token from previous erc20
|
||||
try {
|
||||
logger.debug('calling foreignBridge.chaiToken() to remove it from half duplex tokens list')
|
||||
const chaiToken = await foreignBridge.methods.chaiToken().call()
|
||||
uniqueTokenAddressesSet.delete(chaiToken)
|
||||
} catch (e) {
|
||||
logger.debug('call to foreignBridge.chaiToken() failed')
|
||||
}
|
||||
// Exclude dai token from previous erc20
|
||||
try {
|
||||
logger.debug('calling foreignBridge.erc20token() to remove it from half duplex tokens list')
|
||||
const daiToken = await foreignBridge.methods.erc20token().call()
|
||||
uniqueTokenAddressesSet.delete(daiToken)
|
||||
} catch (e) {
|
||||
logger.debug('call to foreignBridge.erc20token() failed')
|
||||
}
|
||||
|
||||
const uniqueTokenAddresses = [...uniqueTokenAddressesSet]
|
||||
await Promise.all(
|
||||
uniqueTokenAddresses.map(async tokenAddress => {
|
||||
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
|
||||
|
||||
logger.debug('Half duplex token:', tokenAddress)
|
||||
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
|
||||
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
|
||||
event: 'Transfer',
|
||||
fromBlock: MONITOR_FOREIGN_START_BLOCK,
|
||||
toBlock: foreignDelayedBlockNumber,
|
||||
options: {
|
||||
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
|
||||
},
|
||||
chain: 'foreign'
|
||||
})).map(normalizeEvent)
|
||||
|
||||
// Remove events after the ES
|
||||
logger.debug('filtering half duplex transfers happened before ES')
|
||||
const validHalfDuplexTransfers = await filterTransferBeforeES(halfDuplexTransferEvents)
|
||||
|
||||
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]
|
||||
})
|
||||
)
|
||||
|
||||
// filter transfer that is part of a token swap
|
||||
directTransfers = transferEvents.filter(
|
||||
e =>
|
||||
bridgeTokensSwappedEvents.findIndex(
|
||||
t => t.transactionHash === e.referenceTx && e.recipient === ZERO_ADDRESS
|
||||
) === -1
|
||||
)
|
||||
}
|
||||
transferEvents = [...halfDuplexTransferEvents, ...transferEvents]
|
||||
|
||||
// Get transfer events that didn't have a UserRequestForAffirmation event in the same transaction
|
||||
directTransfers = directTransfers.filter(
|
||||
const directTransfers = transferEvents.filter(
|
||||
e => foreignToHomeRequests.findIndex(t => t.referenceTx === e.referenceTx) === -1
|
||||
)
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
|
||||
const blockNumberHalfDuplexDisabled = 9884448
|
||||
|
||||
/**
|
||||
* Returns true if the event was before the bridge stopped supporting half duplex transfers.
|
||||
*/
|
||||
async function transferBeforeES(event) {
|
||||
return event.blockNumber < blockNumberHalfDuplexDisabled
|
||||
}
|
||||
|
||||
async function filterTransferBeforeES(array) {
|
||||
const newArray = []
|
||||
// Iterate events from newer to older
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
const beforeES = await transferBeforeES(array[i])
|
||||
if (beforeES) {
|
||||
// add element to first position so the new array will have the same order
|
||||
newArray.unshift(array[i])
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterTransferBeforeES,
|
||||
blockNumberHalfDuplexDisabled
|
||||
}
|
@ -33,6 +33,10 @@ const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME
|
||||
|
||||
describe('erc to native', () => {
|
||||
before(async () => {
|
||||
console.log('Initializing interest')
|
||||
await foreignBridge.methods
|
||||
.initializeInterest(ercToNativeBridge.foreignToken, 1, 1, validator.address)
|
||||
.send({ from: validator.address, gas: '4000000' })
|
||||
if (process.env.ULTIMATE === 'true') {
|
||||
return
|
||||
}
|
||||
@ -112,6 +116,7 @@ describe('erc to native', () => {
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
await foreignBridge.methods.investDai().send({ from: validator.address, gas: '4000000' })
|
||||
|
||||
// check that balance increases
|
||||
await uniformRetry(async retry => {
|
||||
|
@ -6,6 +6,7 @@ const logger = require('./services/logger')
|
||||
const GasPrice = require('./services/gasPrice')
|
||||
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
|
||||
const { sendTx } = require('./tx/sendTx')
|
||||
const { getTokensState } = require('./utils/tokenState')
|
||||
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
|
||||
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
|
||||
|
||||
@ -17,7 +18,7 @@ if (process.argv.length < 4) {
|
||||
}
|
||||
|
||||
const config = require(path.join('../config/', process.argv[2]))
|
||||
const { web3, eventContract, chain } = config.main
|
||||
const { web3, eventContract, chain, bridgeContract } = config.main
|
||||
|
||||
const isTxHash = txHash => txHash.length === 66 && web3.utils.isHexStrict(txHash)
|
||||
function readTxHashes(filePath) {
|
||||
@ -114,6 +115,12 @@ function processEvents(events) {
|
||||
}
|
||||
|
||||
async function main({ sendJob, txHashes }) {
|
||||
if (config.id === 'erc-native-transfer') {
|
||||
logger.debug('Getting token address to listen Transfer events')
|
||||
const state = await getTokensState(bridgeContract, logger)
|
||||
eventContract.options.address = state.bridgeableTokenAddress
|
||||
}
|
||||
|
||||
logger.info(`Processing ${txHashes.length} input transactions`)
|
||||
for (const txHash of txHashes) {
|
||||
try {
|
||||
|
@ -13,12 +13,25 @@ const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
|
||||
function processTransfersBuilder(config) {
|
||||
const { bridgeContract, web3 } = config.home
|
||||
|
||||
const userRequestForAffirmationAbi = config.foreign.bridgeABI.find(
|
||||
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
||||
)
|
||||
const tokensSwappedAbi = config.foreign.bridgeABI.find(e => e.type === 'event' && e.name === 'TokensSwapped')
|
||||
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
|
||||
const tokensSwappedHash = tokensSwappedAbi ? web3.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
|
||||
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature('UserRequestForAffirmation(address,uint256)')
|
||||
const redeemHash = web3.eth.abi.encodeEventSignature('Redeem(address,uint256,uint256)')
|
||||
const transferHash = web3.eth.abi.encodeEventSignature('Transfer(address,address,uint256)')
|
||||
|
||||
const foreignBridgeAddress = config.foreign.bridgeAddress
|
||||
|
||||
const decodeAddress = data => web3.eth.abi.decodeParameter('address', data)
|
||||
|
||||
const isUserRequestForAffirmation = e =>
|
||||
e.address.toLowerCase() === foreignBridgeAddress.toLowerCase() && e.topics[0] === userRequestForAffirmationHash
|
||||
const isRedeem = cTokenAddress => e =>
|
||||
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
|
||||
e.topics[0] === redeemHash &&
|
||||
decodeAddress(e.data.slice(0, 66)).toLowerCase() === foreignBridgeAddress.toLowerCase()
|
||||
const isCTokenTransfer = cTokenAddress => e =>
|
||||
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
|
||||
e.topics[0] === transferHash &&
|
||||
decodeAddress(e.topics[1]).toLowerCase() === foreignBridgeAddress.toLowerCase() &&
|
||||
decodeAddress(e.topics[2]).toLowerCase() === cTokenAddress.toLowerCase()
|
||||
|
||||
let validatorContract = null
|
||||
|
||||
@ -32,37 +45,35 @@ function processTransfersBuilder(config) {
|
||||
rootLogger.debug(`Processing ${transfers.length} Transfer events`)
|
||||
const callbacks = transfers
|
||||
.map(transfer => async () => {
|
||||
const { from, value } = transfer.returnValues
|
||||
const { from, to, value } = transfer.returnValues
|
||||
|
||||
const logger = rootLogger.child({
|
||||
eventTransactionHash: transfer.transactionHash
|
||||
eventTransactionHash: transfer.transactionHash,
|
||||
from,
|
||||
to,
|
||||
value
|
||||
})
|
||||
|
||||
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
|
||||
logger.info('Processing transfer')
|
||||
|
||||
const receipt = await config.foreign.web3.eth.getTransactionReceipt(transfer.transactionHash)
|
||||
|
||||
const existsAffirmationEvent = receipt.logs.some(
|
||||
e => e.address === config.foreign.bridgeAddress && e.topics[0] === userRequestForAffirmationHash
|
||||
)
|
||||
|
||||
if (existsAffirmationEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
|
||||
transfer.transactionHash
|
||||
}`
|
||||
)
|
||||
if (receipt.logs.some(isUserRequestForAffirmation)) {
|
||||
logger.info('Transfer event discarded because affirmation is detected in the same transaction')
|
||||
return
|
||||
}
|
||||
|
||||
const existsTokensSwappedEvent = tokensSwappedAbi
|
||||
? receipt.logs.some(e => e.address === config.foreign.bridgeAddress && e.topics[0] === tokensSwappedHash)
|
||||
: false
|
||||
if (from === ZERO_ADDRESS) {
|
||||
logger.info('Mint-like transfers from zero address are not processed')
|
||||
return
|
||||
}
|
||||
|
||||
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
|
||||
logger.info(
|
||||
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
|
||||
)
|
||||
// when bridge performs a withdrawal from Compound, the following three events occur
|
||||
// * token.Transfer(from=cToken, to=bridge, amount=X)
|
||||
// * cToken.Redeem(redeemer=bridge, amount=X, tokens=Y)
|
||||
// * cToken.Transfer(from=bridge, to=cToken, amount=Y)
|
||||
if (receipt.logs.some(isRedeem(from)) && receipt.logs.some(isCTokenTransfer(from))) {
|
||||
logger.info('Transfer event discarded because cToken redeem is detected in the same transaction')
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -156,8 +156,6 @@ async function main({ sendToQueue }) {
|
||||
logger.info(`Oracle watcher was unsuspended.`)
|
||||
}
|
||||
|
||||
await checkConditions()
|
||||
|
||||
const lastBlockToProcess = await getLastBlockToProcess(web3, bridgeContract)
|
||||
|
||||
if (lastBlockToProcess <= lastProcessedBlock) {
|
||||
@ -165,6 +163,8 @@ async function main({ sendToQueue }) {
|
||||
return
|
||||
}
|
||||
|
||||
await checkConditions()
|
||||
|
||||
const fromBlock = lastProcessedBlock + 1
|
||||
const rangeEndBlock = config.blockPollingLimit ? fromBlock + config.blockPollingLimit : lastBlockToProcess
|
||||
let toBlock = Math.min(lastBlockToProcess, rangeEndBlock)
|
||||
|
File diff suppressed because one or more lines are too long
33
parity/foreign_mocks/cDAIMock.sol
Normal file
33
parity/foreign_mocks/cDAIMock.sol
Normal file
@ -0,0 +1,33 @@
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
interface IERC20 {
|
||||
function transferFrom(address from,address to,uint256 value) external;
|
||||
function transfer(address to,uint256 value) external;
|
||||
}
|
||||
|
||||
contract cDaiMock {
|
||||
IERC20 daiToken;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint amount);
|
||||
event Mint(address minter, uint mintAmount, uint mintTokens);
|
||||
event Redeem(address redeemer, uint redeemAmount, uint redeemTokens);
|
||||
|
||||
|
||||
function mint(uint256 mintAmount) external returns (uint256) {
|
||||
daiToken.transferFrom(msg.sender, address(this), mintAmount);
|
||||
|
||||
emit Mint(msg.sender, mintAmount, mintAmount);
|
||||
emit Transfer(address(this), msg.sender, mintAmount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function redeemUnderlying(uint256 redeemAmount) external returns (uint256) {
|
||||
daiToken.transfer(msg.sender, redeemAmount);
|
||||
|
||||
emit Transfer(msg.sender, address(this), redeemAmount);
|
||||
emit Redeem(msg.sender, redeemAmount, redeemAmount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user