tokenbridge-contracts/test/erc_to_native/foreign_bridge.test.js

979 lines
41 KiB
JavaScript

const ForeignBridge = artifacts.require('ForeignBridgeErcToNative.sol')
const BridgeValidators = artifacts.require('BridgeValidators.sol')
const EternalStorageProxy = artifacts.require('EternalStorageProxy.sol')
const ERC677BridgeToken = artifacts.require('ERC677BridgeToken.sol')
const ERC20Mock = artifacts.require('ERC20Mock.sol')
const XDaiForeignBridgeMock = artifacts.require('XDaiForeignBridgeMock.sol')
const { expect } = require('chai')
const { ERROR_MSG, ZERO_ADDRESS, toBN } = require('../setup')
const {
createMessage,
sign,
signatureToVRS,
ether,
expectEventInLogs,
createFullAccounts,
packSignatures
} = require('../helpers/helpers')
const getCompoundContracts = require('../compound/contracts')
const halfEther = ether('0.5')
const requireBlockConfirmations = 8
const gasPrice = web3.utils.toWei('1', 'gwei')
const oneEther = ether('1')
const homeDailyLimit = oneEther
const homeMaxPerTx = halfEther
const dailyLimit = oneEther
const maxPerTx = halfEther
const minPerTx = ether('0.01')
const ZERO = toBN(0)
const MAX_VALIDATORS = 50
const MAX_SIGNATURES = MAX_VALIDATORS
const MAX_GAS = 8000000
const decimalShiftZero = 0
contract('ForeignBridge_ERC20_to_Native', async accounts => {
let validatorContract
let authorities
let owner
let token
let otherSideBridge
let dai
const user = accounts[7]
before(async () => {
validatorContract = await BridgeValidators.new()
authorities = [accounts[1], accounts[2]]
owner = accounts[0]
await validatorContract.initialize(1, authorities, owner)
otherSideBridge = await ForeignBridge.new()
dai = await ERC20Mock.new('dai', 'DAI', 18)
await dai.mint(user, ether('100000'))
})
describe('#initialize', async () => {
it('should initialize', async () => {
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const foreignBridge = await ForeignBridge.new()
expect(await foreignBridge.erc20token()).to.be.equal(ZERO_ADDRESS)
expect(await foreignBridge.validatorContract()).to.be.equal(ZERO_ADDRESS)
expect(await foreignBridge.deployedAtBlock()).to.be.bignumber.equal(ZERO)
expect(await foreignBridge.isInitialized()).to.be.equal(false)
expect(await foreignBridge.requiredBlockConfirmations()).to.be.bignumber.equal(ZERO)
expect(await foreignBridge.decimalShift()).to.be.bignumber.equal(ZERO)
await foreignBridge.initialize(
ZERO_ADDRESS,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
ZERO_ADDRESS,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
token.address,
0,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
0,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
owner,
requireBlockConfirmations,
gasPrice,
[maxPerTx, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
owner,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, minPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeMaxPerTx, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
).should.be.rejected
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
'9',
ZERO_ADDRESS
).should.be.rejected
// not valid decimal shift
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
'100',
otherSideBridge.address
).should.be.rejected
const { logs } = await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
'9',
otherSideBridge.address
)
expect(await foreignBridge.erc20token()).to.be.equal(token.address)
expect(await foreignBridge.isInitialized()).to.be.equal(true)
expect(await foreignBridge.validatorContract()).to.be.equal(validatorContract.address)
expect(await foreignBridge.deployedAtBlock()).to.be.bignumber.above(ZERO)
expect(await foreignBridge.requiredBlockConfirmations()).to.be.bignumber.equal(
requireBlockConfirmations.toString()
)
expect(await foreignBridge.dailyLimit()).to.be.bignumber.equal(dailyLimit)
expect(await foreignBridge.maxPerTx()).to.be.bignumber.equal(maxPerTx)
expect(await foreignBridge.minPerTx()).to.be.bignumber.equal(minPerTx)
expect(await foreignBridge.executionDailyLimit()).to.be.bignumber.equal(homeDailyLimit)
expect(await foreignBridge.executionMaxPerTx()).to.be.bignumber.equal(homeMaxPerTx)
expect(await foreignBridge.decimalShift()).to.be.bignumber.equal('9')
expect(await foreignBridge.gasPrice()).to.be.bignumber.equal(gasPrice)
const bridgeMode = '0x18762d46' // 4 bytes of keccak256('erc-to-native-core')
expect(await foreignBridge.getBridgeMode()).to.be.equal(bridgeMode)
const { major, minor, patch } = await foreignBridge.getBridgeInterfacesVersion()
expect(major).to.be.bignumber.gte(ZERO)
expect(minor).to.be.bignumber.gte(ZERO)
expect(patch).to.be.bignumber.gte(ZERO)
expectEventInLogs(logs, 'RequiredBlockConfirmationChanged', {
requiredBlockConfirmations: toBN(requireBlockConfirmations)
})
expectEventInLogs(logs, 'GasPriceChanged', { gasPrice })
expectEventInLogs(logs, 'DailyLimitChanged', { newLimit: dailyLimit })
expectEventInLogs(logs, 'ExecutionDailyLimitChanged', { newLimit: homeDailyLimit })
})
})
describe('#executeSignatures', async () => {
const value = ether('0.25')
let foreignBridge
beforeEach(async () => {
foreignBridge = await ForeignBridge.new()
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
await token.mint(foreignBridge.address, value)
})
it('should allow to executeSignatures', async () => {
const recipientAccount = accounts[3]
const balanceBefore = await token.balanceOf(recipientAccount)
const transactionHash = '0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80'
const message = createMessage(recipientAccount, value, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
const { logs } = await foreignBridge.executeSignatures(message, oneSignature).should.be.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipientAccount)
logs[0].args.value.should.be.bignumber.equal(value)
const balanceAfter = await token.balanceOf(recipientAccount)
const balanceAfterBridge = await token.balanceOf(foreignBridge.address)
balanceAfter.should.be.bignumber.equal(balanceBefore.add(value))
balanceAfterBridge.should.be.bignumber.equal(ZERO)
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
})
it('should allow second withdrawal with different transactionHash but same recipient and value', async () => {
const recipientAccount = accounts[3]
const balanceBefore = await token.balanceOf(recipientAccount)
// tx 1
const value = ether('0.25')
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, value, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
await foreignBridge.executeSignatures(message, oneSignature).should.be.fulfilled
// tx 2
await token.mint(foreignBridge.address, value)
const transactionHash2 = '0x77a496628a776a03d58d7e6059a5937f04bebd8ba4ff89f76dd4bb8ba7e291ee'
const message2 = createMessage(recipientAccount, value, transactionHash2, foreignBridge.address)
const signature2 = await sign(authorities[0], message2)
const vrs2 = signatureToVRS(signature2)
const oneSignature2 = packSignatures([vrs2])
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash2))
const { logs } = await foreignBridge.executeSignatures(message2, oneSignature2).should.be.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipientAccount)
logs[0].args.value.should.be.bignumber.equal(value)
const balanceAfter = await token.balanceOf(recipientAccount)
balanceAfter.should.be.bignumber.equal(balanceBefore.add(value.mul(toBN(2))))
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash2))
})
it('should not allow second withdraw (replay attack) with same transactionHash but different recipient', async () => {
const recipientAccount = accounts[3]
// tx 1
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, value, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
await foreignBridge.executeSignatures(message, oneSignature).should.be.fulfilled
// tx 2
await token.mint(foreignBridge.address, value)
const message2 = createMessage(accounts[4], value, transactionHash, foreignBridge.address)
const signature2 = await sign(authorities[0], message2)
const vrs2 = signatureToVRS(signature2)
const oneSignature2 = packSignatures([vrs2])
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
await foreignBridge.executeSignatures(message2, oneSignature2).should.be.rejectedWith(ERROR_MSG)
})
it('should not allow withdraw over home max tx limit', async () => {
const recipientAccount = accounts[3]
const invalidValue = ether('0.75')
await token.mint(foreignBridge.address, ether('5'))
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, invalidValue, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
await foreignBridge.executeSignatures(message, oneSignature).should.be.rejectedWith(ERROR_MSG)
})
it('should not allow withdraw over daily home limit', async () => {
const recipientAccount = accounts[3]
await token.mint(foreignBridge.address, ether('5'))
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, halfEther, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
await foreignBridge.executeSignatures(message, oneSignature).should.be.fulfilled
const transactionHash2 = '0x69debd8fd1923c9cb3cd8ef6461e2740b2d037943b941729d5a47671a2bb8712'
const message2 = createMessage(recipientAccount, halfEther, transactionHash2, foreignBridge.address)
const signature2 = await sign(authorities[0], message2)
const vrs2 = signatureToVRS(signature2)
const oneSignature2 = packSignatures([vrs2])
await foreignBridge.executeSignatures(message2, oneSignature2).should.be.fulfilled
const transactionHash3 = '0x022695428093bb292db8e48bd1417c5e1b84c0bf673bd0fff23ed0fb6495b872'
const message3 = createMessage(recipientAccount, halfEther, transactionHash3, foreignBridge.address)
const signature3 = await sign(authorities[0], message3)
const vrs3 = signatureToVRS(signature3)
const oneSignature3 = packSignatures([vrs3])
await foreignBridge.executeSignatures(message3, oneSignature3).should.be.rejectedWith(ERROR_MSG)
})
})
describe('#withdraw with 2 minimum signatures', async () => {
let multisigValidatorContract
let twoAuthorities
let ownerOfValidatorContract
let foreignBridgeWithMultiSignatures
const value = halfEther
beforeEach(async () => {
multisigValidatorContract = await BridgeValidators.new()
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
twoAuthorities = [accounts[0], accounts[1]]
ownerOfValidatorContract = accounts[3]
await multisigValidatorContract.initialize(2, twoAuthorities, ownerOfValidatorContract, {
from: ownerOfValidatorContract
})
foreignBridgeWithMultiSignatures = await ForeignBridge.new()
await foreignBridgeWithMultiSignatures.initialize(
multisigValidatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address,
{ from: ownerOfValidatorContract }
)
await token.mint(foreignBridgeWithMultiSignatures.address, value)
})
it('withdraw should fail if not enough signatures are provided', async () => {
const recipientAccount = accounts[4]
// msg 1
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, value, transactionHash, foreignBridgeWithMultiSignatures.address)
const signature = await sign(twoAuthorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
false.should.be.equal(await foreignBridgeWithMultiSignatures.relayedMessages(transactionHash))
await foreignBridgeWithMultiSignatures.executeSignatures(message, oneSignature).should.be.rejectedWith(ERROR_MSG)
// msg 2
const signature2 = await sign(twoAuthorities[1], message)
const vrs2 = signatureToVRS(signature2)
const twoSignatures = packSignatures([vrs, vrs2])
const { logs } = await foreignBridgeWithMultiSignatures.executeSignatures(message, twoSignatures).should.be
.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipientAccount)
logs[0].args.value.should.be.bignumber.equal(value)
true.should.be.equal(await foreignBridgeWithMultiSignatures.relayedMessages(transactionHash))
})
it('withdraw should fail if duplicate signature is provided', async () => {
const recipientAccount = accounts[4]
const transactionHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipientAccount, value, transactionHash, foreignBridgeWithMultiSignatures.address)
const signature = await sign(twoAuthorities[0], message)
const vrs = signatureToVRS(signature)
const twoSignatures = packSignatures([vrs, vrs])
false.should.be.equal(await foreignBridgeWithMultiSignatures.relayedMessages(transactionHash))
await foreignBridgeWithMultiSignatures.executeSignatures(message, twoSignatures).should.be.rejectedWith(ERROR_MSG)
})
it('works with 5 validators and 3 required signatures', async () => {
const recipient = accounts[8]
const authoritiesFiveAccs = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5]]
const ownerOfValidators = accounts[0]
const validatorContractWith3Signatures = await BridgeValidators.new()
await validatorContractWith3Signatures.initialize(3, authoritiesFiveAccs, ownerOfValidators)
const erc20Token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const value = halfEther
const foreignBridgeWithThreeSigs = await ForeignBridge.new()
await foreignBridgeWithThreeSigs.initialize(
validatorContractWith3Signatures.address,
erc20Token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
await erc20Token.mint(foreignBridgeWithThreeSigs.address, value)
const txHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipient, value, txHash, foreignBridgeWithThreeSigs.address)
// signature 1
const signature = await sign(authoritiesFiveAccs[0], message)
const vrs = signatureToVRS(signature)
// signature 2
const signature2 = await sign(authoritiesFiveAccs[1], message)
const vrs2 = signatureToVRS(signature2)
// signature 3
const signature3 = await sign(authoritiesFiveAccs[2], message)
const vrs3 = signatureToVRS(signature3)
const threeSignatures = packSignatures([vrs, vrs2, vrs3])
const { logs } = await foreignBridgeWithThreeSigs.executeSignatures(message, threeSignatures).should.be.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipient)
logs[0].args.value.should.be.bignumber.equal(value)
true.should.be.equal(await foreignBridgeWithThreeSigs.relayedMessages(txHash))
})
it('works with max allowed number of signatures required', async () => {
const recipient = accounts[8]
const value = halfEther
const validatorContract = await BridgeValidators.new()
const authorities = createFullAccounts(web3, MAX_VALIDATORS)
const addresses = authorities.map(account => account.address)
const ownerOfValidators = accounts[0]
await validatorContract.initialize(MAX_SIGNATURES, addresses, ownerOfValidators)
const erc20Token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const foreignBridgeWithMaxSigs = await ForeignBridge.new()
await foreignBridgeWithMaxSigs.initialize(
validatorContract.address,
erc20Token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
await erc20Token.mint(foreignBridgeWithMaxSigs.address, value)
const txHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipient, value, txHash, foreignBridgeWithMaxSigs.address)
const vrsList = []
for (let i = 0; i < MAX_SIGNATURES; i++) {
const { signature } = await authorities[i].sign(message)
vrsList[i] = signatureToVRS(signature)
}
const maxSignatures = packSignatures(vrsList)
const { receipt } = await foreignBridgeWithMaxSigs.executeSignatures(message, maxSignatures).should.be.fulfilled
expect(receipt.gasUsed).to.be.lte(MAX_GAS)
})
})
describe('#upgradeable', async () => {
it('can be upgraded', async () => {
const REQUIRED_NUMBER_OF_VALIDATORS = 1
const VALIDATORS = [accounts[1]]
const PROXY_OWNER = accounts[0]
// Validators Contract
let validatorsProxy = await EternalStorageProxy.new().should.be.fulfilled
const validatorsContractImpl = await BridgeValidators.new().should.be.fulfilled
await validatorsProxy.upgradeTo('1', validatorsContractImpl.address).should.be.fulfilled
validatorsContractImpl.address.should.be.equal(await validatorsProxy.implementation())
validatorsProxy = await BridgeValidators.at(validatorsProxy.address)
await validatorsProxy.initialize(REQUIRED_NUMBER_OF_VALIDATORS, VALIDATORS, PROXY_OWNER).should.be.fulfilled
const token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
// ForeignBridge V1 Contract
let foreignBridgeProxy = await EternalStorageProxy.new().should.be.fulfilled
const foreignBridgeImpl = await ForeignBridge.new().should.be.fulfilled
await foreignBridgeProxy.upgradeTo('1', foreignBridgeImpl.address).should.be.fulfilled
foreignBridgeProxy = await ForeignBridge.at(foreignBridgeProxy.address)
await foreignBridgeProxy.initialize(
validatorsProxy.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
// Deploy V2
const foreignImplV2 = await ForeignBridge.new()
const foreignBridgeProxyUpgrade = await EternalStorageProxy.at(foreignBridgeProxy.address)
await foreignBridgeProxyUpgrade.upgradeTo('2', foreignImplV2.address).should.be.fulfilled
foreignImplV2.address.should.be.equal(await foreignBridgeProxyUpgrade.implementation())
})
it('can be deployed via upgradeToAndCall', async () => {
const tokenAddress = token.address
const validatorsAddress = validatorContract.address
const storageProxy = await EternalStorageProxy.new().should.be.fulfilled
const foreignBridge = await ForeignBridge.new()
const data = foreignBridge.contract.methods
.initialize(
validatorsAddress,
tokenAddress,
requireBlockConfirmations,
gasPrice,
['3', '2', '1'],
['3', '2'],
owner,
decimalShiftZero,
otherSideBridge.address
)
.encodeABI()
await storageProxy.upgradeToAndCall('1', foreignBridge.address, data).should.be.fulfilled
const finalContract = await ForeignBridge.at(storageProxy.address)
true.should.be.equal(await finalContract.isInitialized())
validatorsAddress.should.be.equal(await finalContract.validatorContract())
})
})
describe('#claimTokens', async () => {
it('can send erc20', async () => {
const owner = accounts[0]
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const foreignBridgeImpl = await ForeignBridge.new()
const storageProxy = await EternalStorageProxy.new().should.be.fulfilled
await storageProxy.upgradeTo('1', foreignBridgeImpl.address).should.be.fulfilled
const foreignBridge = await ForeignBridge.at(storageProxy.address)
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
const tokenSecond = await ERC677BridgeToken.new('Roman Token', 'RST', 18)
await tokenSecond.mint(accounts[0], halfEther).should.be.fulfilled
expect(await tokenSecond.balanceOf(accounts[0])).to.be.bignumber.equal(halfEther)
await tokenSecond.transfer(foreignBridge.address, halfEther)
expect(await tokenSecond.balanceOf(accounts[0])).to.be.bignumber.equal(ZERO)
expect(await tokenSecond.balanceOf(foreignBridge.address)).to.be.bignumber.equal(halfEther)
await foreignBridge
.claimTokens(tokenSecond.address, accounts[3], { from: accounts[3] })
.should.be.rejectedWith(ERROR_MSG)
await foreignBridge.claimTokens(tokenSecond.address, accounts[3], { from: owner })
expect(await tokenSecond.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ZERO)
expect(await tokenSecond.balanceOf(accounts[3])).to.be.bignumber.equal(halfEther)
})
})
describe('#decimalShift', async () => {
for (const decimalShift of [2, -1]) {
it(`Home to Foreign: withdraw with 1 signature with a decimalShift of ${decimalShift}`, async () => {
// From a foreign a token erc token 16 decimals.
const token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 16)
const valueOnForeign = toBN('1000')
// Value is decimals shifted from foreign to home: Native on home = 16+2 shift = 18 decimals
const valueOnHome = toBN(valueOnForeign * 10 ** decimalShift)
const owner = accounts[0]
const foreignBridgeImpl = await ForeignBridge.new()
const storageProxy = await EternalStorageProxy.new().should.be.fulfilled
await storageProxy.upgradeTo('1', foreignBridgeImpl.address).should.be.fulfilled
const foreignBridge = await ForeignBridge.at(storageProxy.address)
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShift,
otherSideBridge.address
)
await token.mint(foreignBridge.address, valueOnForeign)
const recipientAccount = accounts[3]
const balanceBefore = await token.balanceOf(recipientAccount)
const transactionHash = '0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80'
const message = createMessage(recipientAccount, valueOnHome, transactionHash, foreignBridge.address)
const signature = await sign(authorities[0], message)
const vrs = signatureToVRS(signature)
const oneSignature = packSignatures([vrs])
false.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
const { logs } = await foreignBridge.executeSignatures(message, oneSignature).should.be.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipientAccount)
logs[0].args.value.should.be.bignumber.equal(valueOnHome)
const balanceAfter = await token.balanceOf(recipientAccount)
balanceAfter.should.be.bignumber.equal(balanceBefore.add(valueOnForeign))
const balanceAfterBridge = await token.balanceOf(foreignBridge.address)
balanceAfterBridge.should.be.bignumber.equal(ZERO)
true.should.be.equal(await foreignBridge.relayedMessages(transactionHash))
})
it(`Home to Foreign: withdraw with 2 minimum signatures with a decimalShift of ${decimalShift}`, async () => {
const multisigValidatorContract = await BridgeValidators.new()
const valueOnForeign = toBN('1000')
const valueOnHome = toBN(valueOnForeign * 10 ** decimalShift)
const token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 16)
const twoAuthorities = [accounts[0], accounts[1]]
const ownerOfValidatorContract = accounts[3]
const recipient = accounts[8]
await multisigValidatorContract.initialize(2, twoAuthorities, ownerOfValidatorContract, {
from: ownerOfValidatorContract
})
const foreignBridgeWithMultiSignatures = await ForeignBridge.new()
await foreignBridgeWithMultiSignatures.initialize(
multisigValidatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShift,
otherSideBridge.address,
{ from: ownerOfValidatorContract }
)
await token.mint(foreignBridgeWithMultiSignatures.address, valueOnForeign)
const balanceBefore = await token.balanceOf(recipient)
const txHash = '0x35d3818e50234655f6aebb2a1cfbf30f59568d8a4ec72066fac5a25dbe7b8121'
const message = createMessage(recipient, valueOnHome, txHash, foreignBridgeWithMultiSignatures.address)
// signature 1
const signature = await sign(twoAuthorities[0], message)
const vrs = signatureToVRS(signature)
// signature 2
const signature2 = await sign(twoAuthorities[1], message)
const vrs2 = signatureToVRS(signature2)
const twoSignatures = packSignatures([vrs, vrs2])
const { logs } = await foreignBridgeWithMultiSignatures.executeSignatures(message, twoSignatures).should.be
.fulfilled
logs[0].event.should.be.equal('RelayedMessage')
logs[0].args.recipient.should.be.equal(recipient)
logs[0].args.value.should.be.bignumber.equal(valueOnHome)
const balanceAfter = await token.balanceOf(recipient)
balanceAfter.should.be.bignumber.equal(balanceBefore.add(valueOnForeign))
const balanceAfterBridge = await token.balanceOf(foreignBridgeWithMultiSignatures.address)
balanceAfterBridge.should.be.bignumber.equal(ZERO)
true.should.be.equal(await foreignBridgeWithMultiSignatures.relayedMessages(txHash))
})
}
})
describe('#relayTokens', () => {
const value = ether('0.25')
const user = accounts[7]
const recipient = accounts[8]
let foreignBridge
beforeEach(async () => {
foreignBridge = await ForeignBridge.new()
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
await foreignBridge.initialize(
validatorContract.address,
token.address,
requireBlockConfirmations,
gasPrice,
[dailyLimit, maxPerTx, minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
await token.mint(user, ether('2'))
})
it('should allow to bridge tokens using approve and relayTokens', async () => {
// Given
const currentDay = await foreignBridge.getCurrentDay()
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(ZERO)
await foreignBridge
.relayTokens(recipient, value, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
await token.approve(foreignBridge.address, value, { from: user }).should.be.fulfilled
// When
await foreignBridge
.relayTokens(ZERO_ADDRESS, value, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
await foreignBridge
.relayTokens(foreignBridge.address, value, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
await foreignBridge
.relayTokens(user, 0, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
const { logs } = await foreignBridge.relayTokens(user, value, {
from: user
}).should.be.fulfilled
// Then
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(value)
expectEventInLogs(logs, 'UserRequestForAffirmation', {
recipient: user,
value
})
})
it('should allow to bridge tokens using approve and relayTokens with different recipient', async () => {
// Given
const currentDay = await foreignBridge.getCurrentDay()
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(ZERO)
await foreignBridge
.relayTokens(recipient, value, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
await token.approve(foreignBridge.address, value, { from: user }).should.be.fulfilled
await foreignBridge
.relayTokens(recipient, 0, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
const { logs } = await foreignBridge.relayTokens(recipient, value, {
from: user
}).should.be.fulfilled
// Then
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(value)
expectEventInLogs(logs, 'UserRequestForAffirmation', {
recipient,
value
})
})
it('should not be able to transfer more than limit', async () => {
// Given
const userSupply = ether('2')
const bigValue = oneEther
const smallValue = ether('0.001')
const currentDay = await foreignBridge.getCurrentDay()
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(ZERO)
await token.approve(foreignBridge.address, userSupply, { from: user }).should.be.fulfilled
// When
// value < minPerTx
await foreignBridge
.relayTokens(recipient, smallValue, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
// value > maxPerTx
await foreignBridge
.relayTokens(recipient, bigValue, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
await foreignBridge.relayTokens(recipient, halfEther, { from: user }).should.be.fulfilled
await foreignBridge.relayTokens(recipient, halfEther, { from: user }).should.be.fulfilled
// totalSpentPerDay > dailyLimit
await foreignBridge
.relayTokens(recipient, halfEther, {
from: user
})
.should.be.rejectedWith(ERROR_MSG)
// Then
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(oneEther)
})
})
describe('compound connector', () => {
const faucet = accounts[6] // account where all Compound-related DAIs where minted
let dai
let cDai
let comptroller
let comp
let foreignBridge
before(async () => {
const contracts = await getCompoundContracts()
dai = contracts.dai
cDai = contracts.cDai
comptroller = contracts.comptroller
comp = contracts.comp
})
beforeEach(async () => {
foreignBridge = await XDaiForeignBridgeMock.new()
await foreignBridge.initialize(
validatorContract.address,
dai.address,
requireBlockConfirmations,
gasPrice,
[ether('1000000'), ether('100000'), minPerTx],
[homeDailyLimit, homeMaxPerTx],
owner,
decimalShiftZero,
otherSideBridge.address
)
await dai.approve(foreignBridge.address, ether('100'), { from: faucet })
await foreignBridge.relayTokens(faucet, ether('10'), { from: faucet })
})
async function generateInterest() {
await cDai.borrow(ether('10'), { from: faucet }).should.be.fulfilled
await comptroller.fastForward(200000).should.be.fulfilled
await cDai.repayBorrow(ether('20'), { from: faucet }).should.be.fulfilled
}
it('should initialize interest', async () => {
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('10'))
expect(await foreignBridge.isInterestEnabled(dai.address)).to.be.equal(false)
const args = [dai.address, oneEther, ether('0.01'), accounts[2]]
await foreignBridge.initializeInterest(...args, { from: user }).should.be.rejected
await foreignBridge.initializeInterest(...args, { from: owner }).should.be.fulfilled
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('10'))
expect(await cDai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ZERO)
expect(await foreignBridge.isInterestEnabled(dai.address)).to.be.equal(true)
expect(await foreignBridge.minCashThreshold(dai.address)).to.be.bignumber.equal(oneEther)
expect(await foreignBridge.minInterestPaid(dai.address)).to.be.bignumber.equal(ether('0.01'))
})
it('should enable and earn interest', async () => {
const initialBalance = await dai.balanceOf(accounts[2])
await foreignBridge.initializeInterest(dai.address, oneEther, ether('0.01'), accounts[2])
expect(await foreignBridge.interestAmount(dai.address)).to.be.bignumber.equal(ZERO)
await foreignBridge.invest(dai.address).should.be.fulfilled
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('1'))
expect(await dai.balanceOf(accounts[2])).to.be.bignumber.equal(initialBalance)
expect(await cDai.balanceOf(foreignBridge.address)).to.be.bignumber.gt(ZERO)
expect(await foreignBridge.interestAmount(dai.address)).to.be.bignumber.equal(ZERO)
await generateInterest()
expect(await foreignBridge.interestAmount(dai.address)).to.be.bignumber.gt(ZERO)
})
it('should pay interest', async () => {
const initialBalance = await dai.balanceOf(accounts[2])
await foreignBridge.initializeInterest(dai.address, oneEther, ether('0.01'), accounts[2])
await foreignBridge.invest(dai.address).should.be.fulfilled
await generateInterest()
expect(await foreignBridge.interestAmount(dai.address)).to.be.bignumber.gt(ether('0.01'))
await foreignBridge.payInterest(dai.address).should.be.fulfilled
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('1'))
expect(await dai.balanceOf(accounts[2])).to.be.bignumber.gt(initialBalance)
expect(await cDai.balanceOf(foreignBridge.address)).to.be.bignumber.gt(ZERO)
expect(await foreignBridge.interestAmount(dai.address)).to.be.bignumber.lt(ether('0.01'))
})
it('should disable interest', async () => {
await foreignBridge.initializeInterest(dai.address, oneEther, ether('0.01'), accounts[2])
await foreignBridge.invest(dai.address).should.be.fulfilled
await generateInterest()
await foreignBridge.payInterest(dai.address).should.be.fulfilled
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('1'))
await foreignBridge.disableInterest(dai.address, { from: user }).should.be.rejected
await foreignBridge.disableInterest(dai.address, { from: owner }).should.be.fulfilled
expect(await dai.balanceOf(foreignBridge.address)).to.be.bignumber.equal(ether('10'))
expect(await cDai.balanceOf(foreignBridge.address)).to.be.bignumber.gt(ZERO)
})
it('configuration', async () => {
await foreignBridge.initializeInterest(dai.address, oneEther, ether('0.01'), accounts[2])
await foreignBridge.setMinCashThreshold(dai.address, ether('2'), { from: user }).should.be.rejected
await foreignBridge.setMinCashThreshold(dai.address, ether('2'), { from: owner }).should.be.fulfilled
expect(await foreignBridge.minCashThreshold(dai.address)).to.be.bignumber.equal(ether('2'))
await foreignBridge.setMinInterestPaid(dai.address, oneEther, { from: user }).should.be.rejected
await foreignBridge.setMinInterestPaid(dai.address, oneEther, { from: owner }).should.be.fulfilled
expect(await foreignBridge.minInterestPaid(dai.address)).to.be.bignumber.equal(oneEther)
await foreignBridge.setInterestReceiver(dai.address, accounts[1], { from: user }).should.be.rejected
await foreignBridge.setInterestReceiver(dai.address, accounts[1], { from: owner }).should.be.fulfilled
expect(await foreignBridge.interestReceiver(dai.address)).to.be.equal(accounts[1])
})
it('should claim comp', async () => {
await foreignBridge.initializeInterest(dai.address, oneEther, ether('0.01'), accounts[2])
await foreignBridge.setMinInterestPaid(comp.address, 1)
await foreignBridge.setInterestReceiver(comp.address, accounts[2])
await foreignBridge.invest(dai.address)
await generateInterest()
const initialBalance = await comp.balanceOf(accounts[2])
await foreignBridge.claimCompAndPay()
expect(await comp.balanceOf(accounts[2])).to.be.bignumber.gt(initialBalance)
})
})
})