import { BigNumberish , BigNumber , constants , Wallet } from 'ethers' ; import { expect , use } from 'chai' ; import { initialSupply } from 'fixtures/trueCurrency' ; import { setupDeploy } from 'scripts/utils' ; import { toAddress , WalletOrAddress , } from 'utils' ; import { beforeEachWithFixture } from 'fixtures/beforeEachWithFixture' ; import { MockTrueCurrencyWithAutosweep , MockTrueCurrencyWithAutosweep__factory , OwnedUpgradeabilityProxy__factory , MockDelegateErc20 , MockDelegateErc20__factory , } from 'contracts' ; import { solidity } from 'ethereum-waffle' ; use ( solidity ) ; describe ( 'TrueCurrency - Delegate ERC20' , ( ) => { let initialHolder ; let secondAccount ; let thirdAccount ; let token ; let delegateToken ; function approve ( tokenOwner , spender , amount ) { const asTokenOwner = token . connect ( tokenOwner ) ; return asTokenOwner . approve ( toAddress ( spender ) , amount ) ; } beforeEachWithFixture ( async ( wallets ) => { [ initialHolder , secondAccount , thirdAccount ] = wallets ; const deployContract = setupDeploy ( initialHolder ) ; const implementation = await deployContract ( MockTrueCurrencyWithAutosweep__factory ) ; const proxy = await deployContract ( OwnedUpgradeabilityProxy__factory ) ; delegateToken = implementation . attach ( proxy . address ) ; await proxy . upgradeTo ( implementation . address ) ; await delegateToken . initialize ( ) ; await delegateToken . mint ( initialHolder . address , initialSupply ) ; token = await new MockDelegateErc20__factory ( initialHolder ) . deploy ( ) ; await delegateToken . setDelegateAddress ( token . address ) ; await token . delegateToNewContract ( delegateToken . address ) ; } ) ; describe ( 'totalSupply' , ( ) => { it ( 'returns the total amount of tokens' , async ( ) => { expect ( await token . totalSupply ( ) ) . to . eq ( initialSupply ) ; } ) ; } ) ; describe ( 'balanceOf' , ( ) => { describe ( 'when the requested account has no tokens' , ( ) => { it ( 'returns zero' , async ( ) => { expect ( await token . balanceOf ( secondAccount . address ) ) . to . eq ( 0 ) ; } ) ; } ) ; describe ( 'when the requested account has some tokens' , ( ) => { it ( 'returns the total amount of tokens' , async ( ) => { expect ( await token . balanceOf ( initialHolder . address ) ) . to . eq ( initialSupply ) ; } ) ; } ) ; } ) ; describe ( 'transfer' , ( ) => { function transfer ( sender , recipient , amount ) { const asSender = token . connect ( sender ) ; return asSender . transfer ( toAddress ( recipient ) , amount ) ; } describe ( 'when the recipient is not the zero address' , ( ) => { describe ( 'when the sender does not have enough balance' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( transfer ( secondAccount , thirdAccount , 100 ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds balance' ) ; } ) ; } ) ; describe ( 'when the sender transfers all balance' , ( ) => { let from ; let to ; const amount = initialSupply ; beforeEach ( ( ) => { from = initialHolder ; to = secondAccount ; } ) ; it ( 'transfers the requested amount' , async ( ) => { await transfer ( from , to , amount ) ; expect ( await token . balanceOf ( from . address ) ) . to . eq ( 0 ) ; expect ( await token . balanceOf ( to . address ) ) . to . eq ( amount ) ; } ) ; it ( 'emits a transfer event' , async ( ) => { await expect ( transfer ( from , to , amount ) ) . to . emit ( delegateToken , 'Transfer' ) . withArgs ( from . address , to . address , amount ) ; } ) ; } ) ; describe ( 'when the sender transfers zero tokens' , ( ) => { let from ; let to ; const amount = 0 ; beforeEach ( ( ) => { from = initialHolder ; to = secondAccount ; } ) ; it ( 'transfers the requested amount' , async ( ) => { await transfer ( from , to , amount ) ; expect ( await token . balanceOf ( from . address ) ) . to . eq ( initialSupply ) ; expect ( await token . balanceOf ( to . address ) ) . to . eq ( 0 ) ; } ) ; it ( 'emits a transfer event' , async ( ) => { await expect ( transfer ( from , to , amount ) ) . to . emit ( delegateToken , 'Transfer' ) . withArgs ( from . address , to . address , amount ) ; } ) ; } ) ; } ) ; describe ( 'when the recipient is the zero address' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( transfer ( initialHolder , constants . AddressZero , initialSupply ) ) . to . be . revertedWith ( 'ERC20: transfer to the zero address' ) ; } ) ; } ) ; } ) ; describe ( 'transferFrom' , ( ) => { function transferFrom ( spender , tokenOwner , recipient , amount ) { const asSpender = token . connect ( spender ) ; return asSpender . transferFrom ( toAddress ( tokenOwner ) , toAddress ( recipient ) , amount ) ; } let spender ; beforeEach ( ( ) => { spender = secondAccount ; } ) ; describe ( 'when the token owner is not the zero address' , ( ) => { let tokenOwner ; beforeEach ( ( ) => { tokenOwner = initialHolder ; } ) ; describe ( 'when the recipient is not the zero address' , ( ) => { let recipient ; beforeEach ( ( ) => { recipient = thirdAccount ; } ) ; describe ( 'when the spender has enough approved balance' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , spender , initialSupply ) ; } ) ; describe ( 'when the token owner has enough balance' , ( ) => { const amount = initialSupply ; it ( 'transfers the requested amount' , async ( ) => { try { await transferFrom ( spender , tokenOwner , recipient , amount ) ; } catch ( e ) { console . log ( e ) ; } expect ( await token . balanceOf ( tokenOwner . address ) ) . to . eq ( 0 ) ; expect ( await token . balanceOf ( recipient . address ) ) . to . eq ( amount ) ; } ) ; it ( 'decreases the spender allowance' , async ( ) => { await transferFrom ( spender , tokenOwner , recipient , amount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( 0 ) ; } ) ; it ( 'emits a transfer event' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . emit ( delegateToken , 'Transfer' ) . withArgs ( tokenOwner . address , recipient . address , amount ) ; } ) ; it ( 'emits an approval event' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . emit ( delegateToken , 'Approval' ) . withArgs ( tokenOwner . address , spender . address , 0 ) ; } ) ; } ) ; describe ( 'when the token owner does not have enough balance' , ( ) => { const amount = initialSupply . add ( 1 ) ; it ( 'reverts' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds balance' ) ; } ) ; } ) ; } ) ; describe ( 'when the spender does not have enough approved balance' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , spender , initialSupply . sub ( 1 ) ) ; } ) ; describe ( 'when the token owner has enough balance' , ( ) => { const amount = initialSupply ; it ( 'reverts' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds allowance' ) ; } ) ; } ) ; describe ( 'when the token owner does not have enough balance' , ( ) => { const amount = initialSupply . add ( 1 ) ; it ( 'reverts' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds balance' ) ; } ) ; } ) ; } ) ; } ) ; describe ( 'when the recipient is the zero address' , ( ) => { const recipient = constants . AddressZero ; const amount = initialSupply ; beforeEach ( async ( ) => { await approve ( tokenOwner , spender , amount ) ; } ) ; it ( 'reverts' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , recipient , amount ) ) . to . be . revertedWith ( 'ERC20: transfer to the zero address' ) ; } ) ; } ) ; } ) ; describe ( 'when the token owner is the zero address' , ( ) => { const tokenOwner = constants . AddressZero ; it ( 'reverts' , async ( ) => { await expect ( transferFrom ( spender , tokenOwner , thirdAccount , 0 ) ) . to . be . revertedWith ( 'ERC20: transfer from the zero address' ) ; } ) ; } ) ; } ) ; describe ( 'approve' , ( ) => { let tokenOwner ; beforeEach ( ( ) => { tokenOwner = initialHolder ; } ) ; describe ( 'when the spender is not the zero address' , ( ) => { let spender ; beforeEach ( ( ) => { spender = secondAccount ; } ) ; function describeApprove ( description , amount ) { describe ( description , ( ) => { it ( 'emits an approval event' , async ( ) => { await expect ( approve ( tokenOwner , spender , amount ) ) . to . emit ( delegateToken , 'Approval' ) . withArgs ( tokenOwner . address , spender . address , amount ) ; } ) ; describe ( 'when there was no approved amount before' , ( ) => { it ( 'approves the requested amount' , async ( ) => { await approve ( tokenOwner , spender , amount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( amount ) ; } ) ; } ) ; describe ( 'when the spender had an approved amount' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , spender , 1 ) ; } ) ; it ( 'approves the requested amount and replaces the previous one' , async ( ) => { await approve ( tokenOwner , spender , amount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( amount ) ; } ) ; } ) ; } ) ; } describeApprove ( 'when the sender has enough balance' , initialSupply ) ; describeApprove ( 'when the sender does not have enough balance' , initialSupply . add ( 1 ) ) ; } ) ; describe ( 'when the spender is the zero address' , ( ) => { const spender = constants . AddressZero ; it ( 'reverts' , async ( ) => { await expect ( approve ( tokenOwner , spender , initialSupply ) ) . to . be . revertedWith ( 'ERC20: approve to the zero address' ) ; } ) ; } ) ; } ) ; describe ( 'decreaseApproval' , ( ) => { function decreaseApproval ( tokenOwner , spender , subtractedValue ) { const asTokenOwner = token . connect ( tokenOwner ) ; return asTokenOwner . decreaseApproval ( toAddress ( spender ) , subtractedValue ) ; } let tokenOwner ; beforeEach ( ( ) => { tokenOwner = initialHolder ; } ) ; describe ( 'when the spender is not the zero address' , ( ) => { let spender ; beforeEach ( ( ) => { spender = secondAccount ; } ) ; function shouldDecreaseApproval ( amount ) { describe ( 'when there was no approved amount before' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( decreaseApproval ( tokenOwner , spender , amount ) ) . to . be . revertedWith ( 'ERC20: decreased allowance below zero' ) ; } ) ; } ) ; describe ( 'when the spender had an approved amount' , ( ) => { const approvedAmount = amount ; beforeEach ( async ( ) => { await approve ( tokenOwner , spender , approvedAmount ) ; } ) ; it ( 'emits an approval event' , async ( ) => { await expect ( decreaseApproval ( tokenOwner , spender , approvedAmount ) ) . to . emit ( delegateToken , 'Approval' ) . withArgs ( tokenOwner . address , spender . address , 0 ) ; } ) ; it ( 'decreases the spender allowance subtracting the requested amount' , async ( ) => { await decreaseApproval ( tokenOwner , spender , approvedAmount . sub ( 1 ) ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( 1 ) ; } ) ; it ( 'sets the allowance to zero when all allowance is removed' , async ( ) => { await decreaseApproval ( tokenOwner , spender , approvedAmount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( 0 ) ; } ) ; it ( 'reverts when more than the full allowance is removed' , async ( ) => { await expect ( decreaseApproval ( tokenOwner , spender , approvedAmount . add ( 1 ) ) ) . to . be . revertedWith ( 'ERC20: decreased allowance below zero' ) ; } ) ; } ) ; } describe ( 'when the sender has enough balance' , ( ) => { shouldDecreaseApproval ( initialSupply ) ; } ) ; describe ( 'when the sender does not have enough balance' , ( ) => { shouldDecreaseApproval ( initialSupply . add ( 1 ) ) ; } ) ; } ) ; describe ( 'when the spender is the zero address' , ( ) => { const spender = constants . AddressZero ; const amount = initialSupply ; it ( 'reverts' , async ( ) => { await expect ( decreaseApproval ( tokenOwner , spender , amount ) ) . to . be . revertedWith ( 'ERC20: decreased allowance below zero' ) ; } ) ; } ) ; } ) ; describe ( 'increaseApproval' , ( ) => { function increaseApproval ( tokenOwner , spender , addedValue ) { const asTokenOwner = token . connect ( tokenOwner ) ; return asTokenOwner . increaseApproval ( toAddress ( spender ) , addedValue ) ; } let tokenOwner ; beforeEach ( ( ) => { tokenOwner = initialHolder ; } ) ; describe ( 'when the spender is not the zero address' , ( ) => { let spender ; beforeEach ( ( ) => { spender = secondAccount ; } ) ; function shouldIncreaseApproval ( amount ) { it ( 'emits an approval event' , async ( ) => { await expect ( increaseApproval ( tokenOwner , spender , amount ) ) . to . emit ( delegateToken , 'Approval' ) . withArgs ( tokenOwner . address , spender . address , amount ) ; } ) ; describe ( 'when there was no approved amount before' , ( ) => { it ( 'approves the requested amount' , async ( ) => { await increaseApproval ( tokenOwner , spender , amount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( amount ) ; } ) ; } ) ; describe ( 'when the spender had an approved amount' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , spender , 1 ) ; } ) ; it ( 'increases the spender allowance adding the requested amount' , async ( ) => { await increaseApproval ( tokenOwner , spender , amount ) ; expect ( await token . allowance ( tokenOwner . address , spender . address ) ) . to . eq ( amount . add ( 1 ) ) ; } ) ; } ) ; } describe ( 'when the sender has enough balance' , ( ) => { shouldIncreaseApproval ( initialSupply ) ; } ) ; describe ( 'when the sender does not have enough balance' , ( ) => { shouldIncreaseApproval ( initialSupply . add ( 1 ) ) ; } ) ; } ) ; describe ( 'when the spender is the zero address' , ( ) => { const spender = constants . AddressZero ; const amount = initialSupply ; it ( 'reverts' , async ( ) => { await expect ( increaseApproval ( tokenOwner , spender , amount ) ) . to . be . revertedWith ( 'ERC20: approve to the zero address' ) ; } ) ; } ) ; } ) ; } ) ;