tokenbridge-contracts/test/poa20_test.js

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))
})