778 lines
36 KiB
JavaScript
778 lines
36 KiB
JavaScript
const POA20 = artifacts.require('ERC677BridgeToken.sol')
|
|
const NoReturnTransferTokenMock = artifacts.require('NoReturnTransferTokenMock.sol')
|
|
const POA20RewardableMock = artifacts.require('ERC677BridgeTokenRewardableMock')
|
|
const ERC677ReceiverTest = artifacts.require('ERC677ReceiverTest.sol')
|
|
const BlockRewardTest = artifacts.require('BlockRewardMock.sol')
|
|
const StakingTest = artifacts.require('Staking.sol')
|
|
const ForeignBridge = artifacts.require('ForeignBridgeErcToNative.sol')
|
|
const BridgeValidators = artifacts.require('BridgeValidators.sol')
|
|
|
|
const { expect } = require('chai')
|
|
const { fromRpcSig } = require('ethereumjs-util')
|
|
|
|
const { ERROR_MSG, ERROR_MSG_OPCODE, ZERO_ADDRESS, BN, toBN } = require('./setup')
|
|
const { ether, expectEventInLogs, deployProxy } = require('./helpers/helpers')
|
|
|
|
async function ethSignTypedData(from, data) {
|
|
const result = await new Promise((res, rej) =>
|
|
web3.currentProvider.send(
|
|
{ jsonrpc: '2.0', method: 'eth_signTypedData', params: [from, data], id: 1 },
|
|
(err, sig) => (err ? rej(err) : res(sig))
|
|
)
|
|
)
|
|
const sig = fromRpcSig(result.result)
|
|
return [sig.v, sig.r, sig.s]
|
|
}
|
|
|
|
async function evmIncreaseTime(delta) {
|
|
return new Promise((res, rej) =>
|
|
web3.currentProvider.send({ jsonrpc: '2.0', method: 'evm_increaseTime', params: [delta], id: 1 }, (err, sig) =>
|
|
err ? rej(err) : res(sig)
|
|
)
|
|
)
|
|
}
|
|
|
|
const minPerTx = ether('0.01')
|
|
const oneEther = ether('1')
|
|
const twoEthers = ether('2')
|
|
const ZERO = new BN(0)
|
|
|
|
function testERC677BridgeToken(accounts, rewardable, permittable, createToken) {
|
|
let token
|
|
const owner = accounts[0]
|
|
const user = accounts[1]
|
|
|
|
async function addBridge(token, bridge, options = { from: owner }) {
|
|
if (rewardable) {
|
|
return token.addBridge(bridge, options)
|
|
}
|
|
return token.setBridgeContract(bridge, options)
|
|
}
|
|
|
|
async function isBridge(token, bridge) {
|
|
if (rewardable) {
|
|
return token.isBridge(bridge)
|
|
}
|
|
return bridge === (await token.bridgeContract())
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
const args = ['POA ERC20 Foundation', 'POA20', 18]
|
|
if (permittable) {
|
|
args.push(1337)
|
|
}
|
|
token = await createToken(args)
|
|
})
|
|
it('default values', async () => {
|
|
expect(await token.symbol()).to.be.equal('POA20')
|
|
expect(await token.decimals()).to.be.bignumber.equal('18')
|
|
expect(await token.name()).to.be.equal('POA ERC20 Foundation')
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
expect(await token.mintingFinished()).to.be.equal(false)
|
|
|
|
const { major, minor, patch } = await token.getTokenInterfacesVersion()
|
|
expect(major).to.be.bignumber.gte(ZERO)
|
|
expect(minor).to.be.bignumber.gte(ZERO)
|
|
expect(patch).to.be.bignumber.gte(ZERO)
|
|
})
|
|
|
|
describe('#bridgeContract', async () => {
|
|
it('can set bridge contract', async () => {
|
|
const bridge = await deployProxy(ForeignBridge)
|
|
expect(await isBridge(token, bridge.address)).to.be.equal(false)
|
|
|
|
await addBridge(token, bridge.address).should.be.fulfilled
|
|
expect(await isBridge(token, bridge.address)).to.be.equal(true)
|
|
})
|
|
|
|
it('only owner can set bridge contract', async () => {
|
|
const bridge = await deployProxy(ForeignBridge)
|
|
expect(await isBridge(token, bridge.address)).to.be.equal(false)
|
|
|
|
await addBridge(token, bridge.address, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
expect(await isBridge(token, bridge.address)).to.be.equal(false)
|
|
|
|
await addBridge(token, bridge.address, { from: owner }).should.be.fulfilled
|
|
expect(await isBridge(token, bridge.address)).to.be.equal(true)
|
|
})
|
|
|
|
it('fail to set invalid bridge contract address', async () => {
|
|
const invalidContractAddress = '0xaaB52d66283F7A1D5978bcFcB55721ACB467384b'
|
|
expect(await isBridge(token, invalidContractAddress)).to.be.equal(false)
|
|
|
|
await addBridge(token, invalidContractAddress).should.be.rejectedWith(ERROR_MSG)
|
|
expect(await isBridge(token, invalidContractAddress)).to.be.equal(false)
|
|
|
|
await addBridge(token, ZERO_ADDRESS).should.be.rejectedWith(ERROR_MSG)
|
|
expect(await isBridge(token, invalidContractAddress)).to.be.equal(false)
|
|
})
|
|
})
|
|
|
|
if (rewardable) {
|
|
describe('#blockRewardContract', async () => {
|
|
it('can set BlockReward contract', async () => {
|
|
const blockRewardContract = await BlockRewardTest.new()
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setBlockRewardContract(blockRewardContract.address).should.be.fulfilled
|
|
;(await token.blockRewardContract()).should.be.equal(blockRewardContract.address)
|
|
})
|
|
|
|
it('only owner can set BlockReward contract', async () => {
|
|
const blockRewardContract = await BlockRewardTest.new()
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token
|
|
.setBlockRewardContract(blockRewardContract.address, { from: user })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setBlockRewardContract(blockRewardContract.address, { from: owner }).should.be.fulfilled
|
|
;(await token.blockRewardContract()).should.be.equal(blockRewardContract.address)
|
|
})
|
|
|
|
it('fail to set invalid BlockReward contract address', async () => {
|
|
const invalidContractAddress = '0xaaB52d66283F7A1D5978bcFcB55721ACB467384b'
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setBlockRewardContract(invalidContractAddress).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setBlockRewardContract(ZERO_ADDRESS).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.blockRewardContract()).should.be.equal(ZERO_ADDRESS)
|
|
})
|
|
})
|
|
|
|
describe('#stakingContract', async () => {
|
|
it('can set Staking contract', async () => {
|
|
const stakingContract = await StakingTest.new()
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setStakingContract(stakingContract.address).should.be.fulfilled
|
|
;(await token.stakingContract()).should.be.equal(stakingContract.address)
|
|
})
|
|
|
|
it('only owner can set Staking contract', async () => {
|
|
const stakingContract = await StakingTest.new()
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setStakingContract(stakingContract.address, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setStakingContract(stakingContract.address, { from: owner }).should.be.fulfilled
|
|
;(await token.stakingContract()).should.be.equal(stakingContract.address)
|
|
})
|
|
|
|
it('fail to set invalid Staking contract address', async () => {
|
|
const invalidContractAddress = '0xaaB52d66283F7A1D5978bcFcB55721ACB467384b'
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setStakingContract(invalidContractAddress).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.setStakingContract(ZERO_ADDRESS).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
})
|
|
|
|
it('fail to set Staking contract address with non-zero balance', async () => {
|
|
const stakingContract = await StakingTest.new()
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
|
|
await token.mint(user, 1, { from: owner }).should.be.fulfilled
|
|
await token.transfer(stakingContract.address, 1, { from: user }).should.be.fulfilled
|
|
|
|
await token.setStakingContract(stakingContract.address).should.be.rejectedWith(ERROR_MSG)
|
|
;(await token.stakingContract()).should.be.equal(ZERO_ADDRESS)
|
|
})
|
|
})
|
|
|
|
describe('#mintReward', async () => {
|
|
it('can only be called by BlockReward contract', async () => {
|
|
await token.setBlockRewardContractMock(accounts[2]).should.be.fulfilled
|
|
await token.mintReward(1, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.mintReward(1, { from: accounts[2] }).should.be.fulfilled
|
|
})
|
|
it('should increase totalSupply and balance', async () => {
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
|
|
await token.setBlockRewardContractMock(user).should.be.fulfilled
|
|
await token.mintReward(100, { from: user }).should.be.fulfilled
|
|
|
|
expect(await token.totalSupply()).to.be.bignumber.equal('100')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal('100')
|
|
})
|
|
})
|
|
|
|
describe('#stake', async () => {
|
|
it('can only be called by Staking contract', async () => {
|
|
await token.setBlockRewardContractMock(user).should.be.fulfilled
|
|
await token.mintReward('100', { from: user }).should.be.fulfilled
|
|
await token.setStakingContractMock(accounts[4]).should.be.fulfilled
|
|
await token.stake(user, '100', { from: accounts[3] }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.stake(user, '100', { from: accounts[4] }).should.be.fulfilled
|
|
})
|
|
it("should revert if user doesn't have enough balance", async () => {
|
|
await token.setBlockRewardContractMock(user).should.be.fulfilled
|
|
await token.mintReward('99', { from: user }).should.be.fulfilled
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal('99')
|
|
await token.setStakingContractMock(accounts[3]).should.be.fulfilled
|
|
await token.stake(user, '100', { from: accounts[3] }).should.be.rejectedWith(ERROR_MSG_OPCODE)
|
|
})
|
|
it("should decrease user's balance and increase Staking's balance", async () => {
|
|
await token.setBlockRewardContractMock(user).should.be.fulfilled
|
|
await token.mintReward('100', { from: user }).should.be.fulfilled
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal('100')
|
|
expect(await token.balanceOf(accounts[3])).to.be.bignumber.equal(ZERO)
|
|
await token.setStakingContractMock(accounts[3]).should.be.fulfilled
|
|
await token.stake(user, '100', { from: accounts[3] }).should.be.fulfilled
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
expect(await token.balanceOf(accounts[3])).to.be.bignumber.equal('100')
|
|
})
|
|
})
|
|
}
|
|
|
|
describe('#mint', async () => {
|
|
it('can mint by owner', async () => {
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
await token.mint(user, 1, { from: owner }).should.be.fulfilled
|
|
expect(await token.totalSupply()).to.be.bignumber.equal('1')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal('1')
|
|
})
|
|
|
|
it('no one can call finishMinting', async () => {
|
|
await token.finishMinting().should.be.rejectedWith(ERROR_MSG)
|
|
})
|
|
|
|
it('cannot mint by non-owner', async () => {
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
await token.mint(user, 1, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
})
|
|
|
|
describe('#transfer', async () => {
|
|
let validatorContract
|
|
beforeEach(async () => {
|
|
validatorContract = await deployProxy(BridgeValidators)
|
|
const authorities = [accounts[2]]
|
|
await validatorContract.initialize(1, authorities, owner)
|
|
})
|
|
it('sends tokens to recipient', async () => {
|
|
await token.mint(user, '1', { from: owner }).should.be.fulfilled
|
|
await token.transfer(user, 1, { from: owner }).should.be.rejectedWith(ERROR_MSG)
|
|
const { logs } = await token.transfer(owner, 1, { from: user }).should.be.fulfilled
|
|
expect(await token.balanceOf(owner)).to.be.bignumber.equal('1')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
expectEventInLogs(logs, 'Transfer', {
|
|
from: user,
|
|
to: owner,
|
|
value: new BN(1)
|
|
})
|
|
})
|
|
|
|
it('sends tokens to bridge contract', async () => {
|
|
const dummyReceiver = await ERC677ReceiverTest.new()
|
|
await token.mint(user, oneEther, { from: owner }).should.be.fulfilled
|
|
|
|
await addBridge(token, dummyReceiver.address).should.be.fulfilled
|
|
const { logs } = await token.transfer(dummyReceiver.address, minPerTx, {
|
|
from: user
|
|
}).should.be.fulfilled
|
|
expectEventInLogs(logs, 'Transfer', {
|
|
from: user,
|
|
to: dummyReceiver.address,
|
|
value: minPerTx
|
|
})
|
|
})
|
|
|
|
if (rewardable) {
|
|
it('fail to send tokens to BlockReward contract directly', async () => {
|
|
const amount = ether('1')
|
|
const blockRewardContractAddress = accounts[2]
|
|
const arbitraryAccountAddress = accounts[3]
|
|
await token.setBlockRewardContractMock(blockRewardContractAddress, { from: owner }).should.be.fulfilled
|
|
await token.mint(user, amount, { from: owner }).should.be.fulfilled
|
|
await token.transfer(blockRewardContractAddress, amount, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.transfer(arbitraryAccountAddress, amount, { from: user }).should.be.fulfilled
|
|
})
|
|
it('fail to send tokens to Staking contract directly', async () => {
|
|
const amount = ether('1')
|
|
const stakingContractAddress = accounts[2]
|
|
const arbitraryAccountAddress = accounts[3]
|
|
await token.setStakingContractMock(stakingContractAddress, { from: owner }).should.be.fulfilled
|
|
await token.mint(user, amount, { from: owner }).should.be.fulfilled
|
|
await token.transfer(stakingContractAddress, amount, { from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.transfer(arbitraryAccountAddress, amount, { from: user }).should.be.fulfilled
|
|
})
|
|
}
|
|
})
|
|
describe('#transferFrom', async () => {
|
|
it('should call onTokenTransfer', async () => {
|
|
const receiver = await ERC677ReceiverTest.new()
|
|
const amount = ether('1')
|
|
const user2 = accounts[2]
|
|
|
|
await addBridge(token, receiver.address).should.be.fulfilled
|
|
|
|
expect(await receiver.from()).to.be.equal(ZERO_ADDRESS)
|
|
expect(await receiver.value()).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.data()).to.be.equal(null)
|
|
|
|
await token.mint(user, amount, { from: owner }).should.be.fulfilled
|
|
await token.approve(user2, amount, { from: user }).should.be.fulfilled
|
|
await token.transferFrom(user, receiver.address, amount, { from: user2 }).should.be.fulfilled
|
|
|
|
expect(await receiver.from()).to.be.equal(user)
|
|
expect(await receiver.value()).to.be.bignumber.equal(amount)
|
|
expect(await receiver.data()).to.be.equal(null)
|
|
})
|
|
if (rewardable) {
|
|
it('fail to send tokens to BlockReward contract directly', async () => {
|
|
const amount = ether('1')
|
|
const user2 = accounts[2]
|
|
const blockRewardContractAddress = accounts[3]
|
|
const arbitraryAccountAddress = accounts[4]
|
|
await token.setBlockRewardContractMock(blockRewardContractAddress, { from: owner }).should.be.fulfilled
|
|
await token.mint(user, amount, { from: owner }).should.be.fulfilled
|
|
await token.approve(user2, amount, { from: user }).should.be.fulfilled
|
|
await token
|
|
.transferFrom(user, blockRewardContractAddress, amount, { from: user2 })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
await token.transferFrom(user, arbitraryAccountAddress, amount, { from: user2 }).should.be.fulfilled
|
|
})
|
|
it('fail to send tokens to Staking contract directly', async () => {
|
|
const amount = ether('1')
|
|
const user2 = accounts[2]
|
|
const stakingContractAddress = accounts[3]
|
|
const arbitraryAccountAddress = accounts[4]
|
|
await token.setStakingContractMock(stakingContractAddress, { from: owner }).should.be.fulfilled
|
|
await token.mint(user, amount, { from: owner }).should.be.fulfilled
|
|
await token.approve(user2, amount, { from: user }).should.be.fulfilled
|
|
await token
|
|
.transferFrom(user, stakingContractAddress, amount, { from: user2 })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
await token.transferFrom(user, arbitraryAccountAddress, amount, { from: user2 }).should.be.fulfilled
|
|
})
|
|
}
|
|
})
|
|
|
|
describe('#increaseAllowance', async () => {
|
|
it('can increase allowance', async () => {
|
|
const twoEther = ether('2')
|
|
const user2 = accounts[2]
|
|
|
|
await token.mint(user, oneEther, { from: owner }).should.be.fulfilled
|
|
await token.approve(user2, oneEther, { from: user }).should.be.fulfilled
|
|
await token.increaseAllowance(user2, oneEther, { from: user }).should.be.fulfilled
|
|
|
|
expect(await token.allowance(user, user2)).to.be.bignumber.equal(twoEther)
|
|
})
|
|
})
|
|
|
|
describe('#decreaseAllowance', async () => {
|
|
it('can decrease allowance', async () => {
|
|
const twoEther = ether('2')
|
|
const user2 = accounts[2]
|
|
|
|
await token.mint(user, twoEther, { from: owner }).should.be.fulfilled
|
|
await token.approve(user2, twoEther, { from: user }).should.be.fulfilled
|
|
await token.decreaseAllowance(user2, oneEther, { from: user }).should.be.fulfilled
|
|
|
|
expect(await token.allowance(user, user2)).to.be.bignumber.equal(oneEther)
|
|
})
|
|
})
|
|
|
|
describe('#burn', async () => {
|
|
it('can burn', async () => {
|
|
await token.burn(100, { from: owner }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.mint(user, 1, { from: owner }).should.be.fulfilled
|
|
await token.burn(1, { from: user }).should.be.fulfilled
|
|
expect(await token.totalSupply()).to.be.bignumber.equal(ZERO)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
})
|
|
|
|
describe('#transferAndCall', () => {
|
|
let validatorContract
|
|
beforeEach(async () => {
|
|
validatorContract = await deployProxy(BridgeValidators)
|
|
const authorities = [accounts[2]]
|
|
await validatorContract.initialize(1, authorities, owner)
|
|
})
|
|
it('calls contractFallback', async () => {
|
|
const receiver = await ERC677ReceiverTest.new()
|
|
expect(await receiver.from()).to.be.equal(ZERO_ADDRESS)
|
|
expect(await receiver.value()).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.data()).to.be.equal(null)
|
|
expect(await receiver.someVar()).to.be.bignumber.equal(ZERO)
|
|
|
|
const callDoSomething123 = receiver.contract.methods.doSomething(123).encodeABI()
|
|
await token.mint(user, '1', { from: owner }).should.be.fulfilled
|
|
await token
|
|
.transferAndCall(token.address, '1', callDoSomething123, { from: user })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
await token
|
|
.transferAndCall(ZERO_ADDRESS, '1', callDoSomething123, { from: user })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
await token.transferAndCall(receiver.address, '1', callDoSomething123, { from: user }).should.be.fulfilled
|
|
expect(await token.balanceOf(receiver.address)).to.be.bignumber.equal('1')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.from()).to.be.equal(user)
|
|
expect(await receiver.value()).to.be.bignumber.equal('1')
|
|
expect(await receiver.data()).to.be.equal(callDoSomething123)
|
|
expect(await receiver.someVar()).to.be.bignumber.equal('123')
|
|
})
|
|
|
|
it('sends tokens to bridge contract', async () => {
|
|
const dummyReceiver = await ERC677ReceiverTest.new()
|
|
await token.mint(user, oneEther, { from: owner }).should.be.fulfilled
|
|
|
|
await addBridge(token, dummyReceiver.address).should.be.fulfilled
|
|
const { logs } = await token.transferAndCall(dummyReceiver.address, minPerTx, '0x', { from: user }).should.be
|
|
.fulfilled
|
|
expectEventInLogs(logs, 'Transfer', {
|
|
from: user,
|
|
to: dummyReceiver.address,
|
|
value: minPerTx
|
|
})
|
|
})
|
|
|
|
it('fail to sends tokens to contract that does not contains onTokenTransfer method', async () => {
|
|
await token.mint(user, oneEther, { from: owner }).should.be.fulfilled
|
|
|
|
await token
|
|
.transferAndCall(validatorContract.address, minPerTx, '0x', { from: user })
|
|
.should.be.rejectedWith(ERROR_MSG)
|
|
})
|
|
})
|
|
describe('#claimtokens', async () => {
|
|
it('can take send ERC20 tokens', async () => {
|
|
const owner = accounts[0]
|
|
const halfEther = ether('0.5')
|
|
|
|
const args = ['Roman Token', 'RST', 18]
|
|
if (permittable) {
|
|
args.push(100)
|
|
}
|
|
const tokenSecond = await createToken(args)
|
|
|
|
await tokenSecond.mint(accounts[0], halfEther).should.be.fulfilled
|
|
halfEther.should.be.bignumber.equal(await tokenSecond.balanceOf(accounts[0]))
|
|
await tokenSecond.transfer(token.address, halfEther)
|
|
expect(await tokenSecond.balanceOf(accounts[0])).to.be.bignumber.equal(ZERO)
|
|
expect(await tokenSecond.balanceOf(token.address)).to.be.bignumber.equal(halfEther)
|
|
|
|
await token.claimTokens(tokenSecond.address, accounts[3], { from: owner })
|
|
expect(await tokenSecond.balanceOf(token.address)).to.be.bignumber.equal(ZERO)
|
|
halfEther.should.be.bignumber.equal(await tokenSecond.balanceOf(accounts[3]))
|
|
})
|
|
it('works with token that not return on transfer', async () => {
|
|
const owner = accounts[0]
|
|
const halfEther = ether('0.5')
|
|
const tokenMock = await NoReturnTransferTokenMock.new()
|
|
|
|
await tokenMock.mint(accounts[0], halfEther).should.be.fulfilled
|
|
expect(await tokenMock.balanceOf(accounts[0])).to.be.bignumber.equal(halfEther)
|
|
|
|
await tokenMock.transfer(token.address, halfEther).should.be.fulfilled
|
|
expect(await tokenMock.balanceOf(accounts[0])).to.be.bignumber.equal(ZERO)
|
|
expect(await tokenMock.balanceOf(token.address)).to.be.bignumber.equal(halfEther)
|
|
|
|
await token.claimTokens(tokenMock.address, accounts[3], { from: owner }).should.be.fulfilled
|
|
expect(await tokenMock.balanceOf(token.address)).to.be.bignumber.equal(ZERO)
|
|
expect(await tokenMock.balanceOf(accounts[3])).to.be.bignumber.equal(halfEther)
|
|
})
|
|
})
|
|
describe('#transfer', async () => {
|
|
it('if transfer called on contract, onTokenTransfer is not invoked', async () => {
|
|
const receiver = await ERC677ReceiverTest.new()
|
|
expect(await receiver.from()).to.be.equal(ZERO_ADDRESS)
|
|
expect(await receiver.value()).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.data()).to.be.equal(null)
|
|
expect(await receiver.someVar()).to.be.bignumber.equal(ZERO)
|
|
|
|
await token.mint(user, 1, { from: owner }).should.be.fulfilled
|
|
const { logs } = await token.transfer(receiver.address, '1', { from: user }).should.be.fulfilled
|
|
|
|
expect(await token.balanceOf(receiver.address)).to.be.bignumber.equal('1')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.from()).to.be.equal(ZERO_ADDRESS)
|
|
expect(await receiver.value()).to.be.bignumber.equal(ZERO)
|
|
expect(await receiver.data()).to.be.equal(null)
|
|
expect(logs[0].event).to.be.equal('Transfer')
|
|
})
|
|
it('if transfer called on contract, still works even if onTokenTransfer doesnot exist', async () => {
|
|
const args = ['Some', 'Token', 18]
|
|
if (permittable) {
|
|
args.push(100)
|
|
}
|
|
const someContract = await createToken(args)
|
|
await token.mint(user, '2', { from: owner }).should.be.fulfilled
|
|
const tokenTransfer = await token.transfer(someContract.address, '1', { from: user }).should.be.fulfilled
|
|
const tokenTransfer2 = await token.transfer(accounts[0], '1', { from: user }).should.be.fulfilled
|
|
expect(await token.balanceOf(someContract.address)).to.be.bignumber.equal('1')
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(ZERO)
|
|
tokenTransfer.logs[0].event.should.be.equal('Transfer')
|
|
tokenTransfer2.logs[0].event.should.be.equal('Transfer')
|
|
})
|
|
})
|
|
describe('#renounceOwnership', () => {
|
|
it('should not be able to renounce ownership', async () => {
|
|
await token.renounceOwnership({ from: user }).should.be.rejectedWith(ERROR_MSG)
|
|
await token.renounceOwnership().should.be.rejectedWith(ERROR_MSG)
|
|
})
|
|
})
|
|
if (permittable) {
|
|
describe('permit', () => {
|
|
const EIP712Domain = [
|
|
{ name: 'name', type: 'string' },
|
|
{ name: 'version', type: 'string' },
|
|
{ name: 'chainId', type: 'uint256' },
|
|
{ name: 'verifyingContract', type: 'address' }
|
|
]
|
|
let domain
|
|
let permit
|
|
let permitLegacy
|
|
|
|
const makeLegacyMsg = (nonce, expiry, allowed) => ({
|
|
types: {
|
|
EIP712Domain,
|
|
Permit: [
|
|
{ name: 'holder', type: 'address' },
|
|
{ name: 'spender', type: 'address' },
|
|
{ name: 'nonce', type: 'uint256' },
|
|
{ name: 'expiry', type: 'uint256' },
|
|
{ name: 'allowed', type: 'bool' }
|
|
]
|
|
},
|
|
primaryType: 'Permit',
|
|
domain,
|
|
message: {
|
|
holder: owner,
|
|
spender: user,
|
|
nonce,
|
|
expiry,
|
|
allowed
|
|
}
|
|
})
|
|
const makeMsg = (nonce, deadline, value) => ({
|
|
types: {
|
|
EIP712Domain,
|
|
Permit: [
|
|
{ name: 'owner', type: 'address' },
|
|
{ name: 'spender', type: 'address' },
|
|
{ name: 'value', type: 'uint256' },
|
|
{ name: 'nonce', type: 'uint256' },
|
|
{ name: 'deadline', type: 'uint256' }
|
|
]
|
|
},
|
|
primaryType: 'Permit',
|
|
domain,
|
|
message: {
|
|
owner,
|
|
spender: user,
|
|
value: value || oneEther.toString(),
|
|
nonce,
|
|
deadline
|
|
}
|
|
})
|
|
|
|
beforeEach(() => {
|
|
domain = {
|
|
name: 'POA ERC20 Foundation',
|
|
version: '1',
|
|
chainId: 1337,
|
|
verifyingContract: token.address
|
|
}
|
|
permit = token.methods['permit(address,address,uint256,uint256,uint8,bytes32,bytes32)']
|
|
permitLegacy = token.methods['permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)']
|
|
})
|
|
|
|
describe('legacy permit', () => {
|
|
const INFINITY = new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
|
|
|
|
it('should accept signed message', async () => {
|
|
const expiry = 10000000000
|
|
const sig1 = await ethSignTypedData(owner, makeLegacyMsg(0, 100, true))
|
|
const sig2 = await ethSignTypedData(owner, makeLegacyMsg(1, expiry, true))
|
|
const sig3 = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
|
|
await permitLegacy(owner, user, 0, 100, true, ...sig1).should.be.rejected // too small deadline
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig2).should.be.rejected // invalid nonce
|
|
await permitLegacy(owner, user, 1, expiry, true, ...sig2).should.be.rejected // not current nonce
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig3).should.be.fulfilled // valid for nonce == 0
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig3).should.be.rejected // invalid duplicate, invalid nonce
|
|
await permitLegacy(owner, user, 1, expiry, true, ...sig3).should.be.rejected // invalid nonce
|
|
await permitLegacy(user, user, 1, expiry, true, ...sig2).should.be.rejected // invalid sender
|
|
await permitLegacy(owner, owner, 1, expiry, true, ...sig2).should.be.rejected // invalid receiver
|
|
await permitLegacy(owner, user, 1, expiry + 1, true, ...sig2).should.be.rejected // invalid expiry
|
|
await permitLegacy(owner, user, 1, expiry, false, ...sig2).should.be.rejected // invalid allowed
|
|
await permitLegacy(owner, user, 1, expiry, true, ...sig2).should.be.fulfilled // valid for nonce == 1
|
|
await permitLegacy(owner, user, 1, expiry, true, ...sig2).should.be.rejected // invalid duplicate, invalid nonce
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.nonces(owner)).to.be.bignumber.equal('2')
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
})
|
|
|
|
it('should cancel expirations on infinite approval from approve()', async () => {
|
|
const expiry = 10000000000
|
|
const sig = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await token.approve(user, 1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal('1')
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await token.approve(user, INFINITY).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
|
|
it('should cancel expirations on infinite approval from increaseAllowance()', async () => {
|
|
const expiry = 10000000000
|
|
const sig = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await token.approve(user, 1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal('1')
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await token.increaseAllowance(user, 1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal('2')
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await token.increaseAllowance(user, INFINITY.subn(2)).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
|
|
it('should cancel expirations on infinite approval from permit()', async () => {
|
|
const expiry = 10000000000
|
|
const sig1 = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
const sig2 = await ethSignTypedData(owner, makeMsg(1, expiry))
|
|
const sig3 = await ethSignTypedData(owner, makeMsg(2, expiry, INFINITY.toString()))
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await permit(owner, user, oneEther, expiry, ...sig2).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(oneEther)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await permit(owner, user, INFINITY, expiry, ...sig3).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
|
|
it('should cancel approval when allowed is false', async () => {
|
|
const expiry = 10000000000
|
|
const sig1 = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
const sig2 = await ethSignTypedData(owner, makeLegacyMsg(1, expiry, false))
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(toBN(expiry))
|
|
|
|
await permitLegacy(owner, user, 1, expiry, false, ...sig2).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(ZERO)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
|
|
it('should accept infinite approval without deadline', async () => {
|
|
const sig1 = await ethSignTypedData(owner, makeLegacyMsg(0, 0, true))
|
|
const sig2 = await ethSignTypedData(owner, makeLegacyMsg(1, 0, false))
|
|
await permitLegacy(owner, user, 0, 0, true, ...sig1).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
|
|
await permitLegacy(owner, user, 1, 0, false, ...sig2).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(ZERO)
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
|
|
it('should allow to use allowance without deadline', async () => {
|
|
await token.mint(owner, ether('10')).should.be.fulfilled
|
|
const sig = await ethSignTypedData(owner, makeLegacyMsg(0, 0, true))
|
|
await permitLegacy(owner, user, 0, 0, true, ...sig).should.be.fulfilled
|
|
|
|
await token.transferFrom(owner, user, oneEther, { from: user }).should.be.fulfilled
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(oneEther)
|
|
expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('9'))
|
|
})
|
|
|
|
it('should not allow to use approval after deadline', async () => {
|
|
await token.mint(owner, ether('10')).should.be.fulfilled
|
|
const expiry = 10000000000
|
|
const sig = await ethSignTypedData(owner, makeLegacyMsg(0, expiry, true))
|
|
await permitLegacy(owner, user, 0, expiry, true, ...sig).should.be.fulfilled
|
|
|
|
await token.transferFrom(owner, user, oneEther, { from: user }).should.be.fulfilled
|
|
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(oneEther)
|
|
expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('9'))
|
|
|
|
await evmIncreaseTime(expiry)
|
|
|
|
await token.transferFrom(owner, user, oneEther, { from: user }).should.be.rejected
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(INFINITY)
|
|
expect(await token.balanceOf(user)).to.be.bignumber.equal(oneEther)
|
|
expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('9'))
|
|
})
|
|
})
|
|
|
|
describe('ERC2612', () => {
|
|
it('should accept signed message', async () => {
|
|
const deadline = 100000000000
|
|
const sig1 = await ethSignTypedData(owner, makeMsg(0, 100))
|
|
const sig2 = await ethSignTypedData(owner, makeMsg(1, deadline))
|
|
const sig3 = await ethSignTypedData(owner, makeMsg(0, deadline))
|
|
|
|
await permit(owner, user, oneEther, 100, ...sig1).should.be.rejected // too small deadline
|
|
await permit(owner, user, oneEther, deadline, ...sig2).should.be.rejected // invalid nonce
|
|
await permit(owner, user, oneEther, deadline, ...sig3).should.be.fulfilled // valid for nonce == 0
|
|
await permit(owner, user, oneEther, deadline, ...sig3).should.be.rejected // invalid duplicate, invalid nonce
|
|
await permit(user, user, oneEther, deadline, ...sig2).should.be.rejected // invalid sender
|
|
await permit(owner, owner, oneEther, deadline, ...sig2).should.be.rejected // invalid receiver
|
|
await permit(owner, user, twoEthers, deadline, ...sig2).should.be.rejected // invalud value
|
|
await permit(owner, user, oneEther, deadline + 1, ...sig2).should.be.rejected // invalid deadline
|
|
await permit(owner, user, oneEther, deadline, ...sig2).should.be.fulfilled // valid for nonce == 1
|
|
await permit(owner, user, oneEther, deadline, ...sig2).should.be.rejected // invalid duplicate, invalid nonce
|
|
expect(await token.allowance(owner, user)).to.be.bignumber.equal(oneEther)
|
|
expect(await token.nonces(owner)).to.be.bignumber.equal('2')
|
|
expect(await token.expirations(owner, user)).to.be.bignumber.equal(ZERO)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
contract('ERC677BridgeToken', accounts => {
|
|
testERC677BridgeToken(accounts, false, false, args => POA20.new(...args))
|
|
})
|
|
|
|
contract('ERC677BridgeTokenRewardable', accounts => {
|
|
testERC677BridgeToken(accounts, true, true, args => POA20RewardableMock.new(...args))
|
|
})
|