import { BigNumber , BigNumberish , constants , providers , Wallet } from 'ethers' ; import { solidity } from 'ethereum-waffle' ; import { expect , use } from 'chai' ; import { initialSupply , setupTrueGold } from 'fixtures/trueGold' ; import { loadFixture } from 'fixtures/beforeEachWithFixture' ; import { toAddress , WalletOrAddress } from 'utils' ; import { TrueGold } from 'contracts' ; use ( solidity ) ; describe ( 'TrueGold - TrueMintableBurnable' , ( ) => { const redemptionAddress = '0x0000000000000000000000000000000000074D72' ; let deployer ; let initialHolder ; let secondAccount ; let token ; function approve ( tokenOwner , spender , amount ) { const asTokenOwner = token . connect ( tokenOwner ) ; return asTokenOwner . approve ( toAddress ( spender ) , amount ) ; } beforeEach ( async ( ) => { ( { deployer , initialHolder , secondAccount , token } = await loadFixture ( setupTrueGold ) ) ; } ) ; describe ( 'transfers to redemption addresses' , ( ) => { const initialBalance = initialSupply ; let tokenOwner ; let burner ; beforeEach ( async ( ) => { tokenOwner = initialHolder ; burner = secondAccount ; } ) ; describe ( 'transfer' , ( ) => { function transfer ( sender , recipient , amount ) { return token . connect ( sender ) . transfer ( toAddress ( recipient ) , amount ) ; } describe ( 'when the given amount is not greater than balance of the sender' , ( ) => { describe ( 'for a zero amount' , ( ) => { shouldBurn ( 0 ) ; } ) ; describe ( 'for a non-zero amount' , ( ) => { shouldBurn ( 12441000 ) ; } ) ; function shouldBurn ( amount ) { it ( 'burns the requested amount' , async ( ) => { await transfer ( tokenOwner , redemptionAddress , amount ) ; expect ( await token . balanceOf ( tokenOwner . address ) ) . to . eq ( initialBalance . sub ( amount ) ) ; expect ( await token . totalSupply ( ) ) . to . eq ( initialBalance . sub ( amount ) ) ; } ) ; it ( 'emits transfer and burn events' , async ( ) => { await expect ( transfer ( tokenOwner , redemptionAddress , amount ) ) . to . emit ( token , 'Transfer' ) . withArgs ( tokenOwner . address , redemptionAddress , amount ) . and . to . emit ( token , 'Transfer' ) . withArgs ( redemptionAddress , constants . AddressZero , amount ) . and . to . emit ( token , 'Burn' ) . withArgs ( redemptionAddress , amount ) ; } ) ; } } ) ; describe ( 'when the given amount is greater than the balance of the sender' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( transfer ( tokenOwner , redemptionAddress , initialBalance . add ( 12441000 ) ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds balance' ) ; } ) ; } ) ; } ) ; describe ( 'transferFrom' , ( ) => { function transferFrom ( spender , tokenOwner , recipient , amount ) { return token . connect ( spender ) . transferFrom ( toAddress ( tokenOwner ) , toAddress ( recipient ) , amount ) ; } describe ( 'on success' , ( ) => { describe ( 'for a zero amount' , ( ) => { shouldBurnFrom ( BigNumber . from ( 0 ) ) ; } ) ; describe ( 'for a non-zero amount' , ( ) => { shouldBurnFrom ( BigNumber . from ( 12441000 ) ) ; } ) ; function shouldBurnFrom ( amount ) { const originalAllowance = amount . mul ( 3 ) ; beforeEach ( async ( ) => { await approve ( tokenOwner , burner , originalAllowance ) ; } ) ; it ( 'burns the requested amount' , async ( ) => { await transferFrom ( burner , tokenOwner , redemptionAddress , amount ) ; expect ( await token . balanceOf ( tokenOwner . address ) ) . to . eq ( initialBalance . sub ( amount ) ) ; expect ( await token . totalSupply ( ) ) . to . eq ( initialBalance . sub ( amount ) ) ; } ) ; it ( 'decrements allowance' , async ( ) => { await transferFrom ( burner , tokenOwner , redemptionAddress , amount ) ; expect ( await token . allowance ( tokenOwner . address , burner . address ) ) . to . eq ( originalAllowance . sub ( amount ) ) ; } ) ; it ( 'emits transfer and burn events' , async ( ) => { await expect ( transferFrom ( burner , tokenOwner , redemptionAddress , amount ) ) . to . emit ( token , 'Transfer' ) . withArgs ( tokenOwner . address , redemptionAddress , amount ) . and . to . emit ( token , 'Transfer' ) . withArgs ( redemptionAddress , constants . AddressZero , amount ) . and . to . emit ( token , 'Burn' ) . withArgs ( redemptionAddress , amount ) ; } ) ; } } ) ; describe ( 'when the given amount is greater than the balance of the sender' , ( ) => { const amount = initialBalance . add ( 12441000 ) ; it ( 'reverts' , async ( ) => { await approve ( tokenOwner , burner , amount ) ; await expect ( transferFrom ( burner , tokenOwner , redemptionAddress , amount ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds balance' ) ; } ) ; } ) ; describe ( 'when the given amount is greater than the allowance' , ( ) => { const allowance = BigNumber . from ( 12441000 ) ; it ( 'reverts' , async ( ) => { await approve ( tokenOwner , burner , allowance ) ; await expect ( transferFrom ( burner , tokenOwner , redemptionAddress , allowance . mul ( 2 ) ) ) . to . be . revertedWith ( 'ERC20: transfer amount exceeds allowance' ) ; } ) ; } ) ; } ) ; } ) ; describe ( 'burn bounds' , ( ) => { const initialBalance = initialSupply ; let tokenOwner ; let burner ; beforeEach ( async ( ) => { tokenOwner = initialHolder ; burner = secondAccount ; await token . setBurnBounds ( 12441000 * 2 , 12441000 * 3 ) ; } ) ; function shouldRespectBurnBounds ( burn ) { describe ( 'when the burn amount is below min bound' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( burn ( 12441000 ) ) . to . be . revertedWith ( 'TrueMintableBurnable: burn amount below min bound' ) ; } ) ; } ) ; describe ( 'when the burn amount is above max bound' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( burn ( 12441000 * 4 ) ) . to . be . revertedWith ( 'TrueMintableBurnable: burn amount exceeds max bound' ) ; } ) ; } ) ; describe ( 'when the burn amount is within bounds' , ( ) => { it ( 'burns the requested amount' , async ( ) => { await burn ( 12441000 * 2 ) ; expect ( await token . balanceOf ( tokenOwner . address ) ) . to . eq ( initialBalance . sub ( 12441000 * 2 ) ) ; expect ( await token . totalSupply ( ) ) . to . eq ( initialSupply . sub ( 12441000 * 2 ) ) ; } ) ; } ) ; } describe ( 'burn' , ( ) => { shouldRespectBurnBounds ( amount => token . connect ( tokenOwner ) . burn ( amount ) ) ; } ) ; describe ( 'burnFrom' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , burner , 12441000 * 4 ) ; } ) ; shouldRespectBurnBounds ( amount => token . connect ( burner ) . burnFrom ( tokenOwner . address , amount ) ) ; } ) ; describe ( 'transfer' , ( ) => { shouldRespectBurnBounds ( amount => token . connect ( tokenOwner ) . transfer ( redemptionAddress , amount ) ) ; } ) ; describe ( 'transferFrom' , ( ) => { beforeEach ( async ( ) => { await approve ( tokenOwner , burner , 12441000 * 4 ) ; } ) ; shouldRespectBurnBounds ( amount => token . connect ( burner ) . transferFrom ( tokenOwner . address , redemptionAddress , amount ) ) ; } ) ; } ) ; describe ( 'setBurnBounds' , ( ) => { function setBurnBounds ( caller , minAmount , maxAmount ) { return token . connect ( caller ) . setBurnBounds ( minAmount , maxAmount ) ; } let owner ; let other ; beforeEach ( ( ) => { owner = deployer ; other = secondAccount ; } ) ; describe ( 'when the caller is the contract owner' , ( ) => { describe ( 'when min amount is less or equal to max amount' , ( ) => { it ( 'sets the new burn bounds' , async ( ) => { await setBurnBounds ( owner , 12441000 , 12441000 * 2 ) ; expect ( await token . burnMin ( ) ) . to . eq ( 12441000 ) ; expect ( await token . burnMax ( ) ) . to . eq ( 12441000 * 2 ) ; } ) ; it ( 'emits set event' , async ( ) => { await expect ( setBurnBounds ( owner , 12441000 , 12441000 * 2 ) ) . to . emit ( token , 'SetBurnBounds' ) . withArgs ( 12441000 , 12441000 * 2 ) ; } ) ; } ) ; describe ( 'when min amount is greater than max amount' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( setBurnBounds ( owner , 12441000 * 2 , 0 ) ) . to . be . revertedWith ( 'TrueMintableBurnable: min is greater then max' ) ; } ) ; } ) ; } ) ; describe ( 'when the caller is not the contract owner' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( setBurnBounds ( other , 0 , 12441000 * 2 ) ) . to . be . revertedWith ( 'Ownable: caller is not the owner' ) ; } ) ; } ) ; } ) ; describe ( 'mint' , ( ) => { function mint ( caller , to , amount ) { return token . connect ( caller ) . mint ( toAddress ( to ) , amount ) ; } let owner ; let other ; beforeEach ( ( ) => { owner = deployer ; other = secondAccount ; } ) ; describe ( 'when the caller is the contract owner' , ( ) => { describe ( 'when the target account is not redemption or zero address' , ( ) => { describe ( 'for a zero amount' , ( ) => { shouldMint ( BigNumber . from ( 0 ) ) ; } ) ; describe ( 'for a non-zero amount' , ( ) => { shouldMint ( BigNumber . from ( 100 ) ) ; } ) ; function shouldMint ( amount ) { it ( 'mints the requested amount' , async ( ) => { const initialSupply = await token . totalSupply ( ) ; const initialBalance = await token . balanceOf ( other . address ) ; await mint ( owner , other , amount ) ; expect ( await token . totalSupply ( ) ) . to . eq ( initialSupply . add ( amount ) ) ; expect ( await token . balanceOf ( other . address ) ) . to . eq ( initialBalance . add ( amount ) ) ; } ) ; it ( 'emits transfer and mint events' , async ( ) => { await expect ( mint ( owner , other , amount ) ) . to . emit ( token , 'Transfer' ) . withArgs ( constants . AddressZero , other . address , amount ) . and . to . emit ( token , 'Mint' ) . withArgs ( other . address , amount ) ; } ) ; } } ) ; describe ( 'when the target account is a redemption address' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( mint ( owner , redemptionAddress , 100 ) ) . to . be . revertedWith ( 'TrueMintableBurnable: mint to a redemption or zero address' ) ; } ) ; } ) ; describe ( 'when the target account is the zero address' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( mint ( owner , constants . AddressZero , 100 ) ) . to . be . revertedWith ( 'TrueMintableBurnable: mint to a redemption or zero address' ) ; } ) ; } ) ; } ) ; describe ( 'when the caller in not the contract owner' , ( ) => { it ( 'reverts' , async ( ) => { await expect ( mint ( other , other , 100 ) ) . to . be . revertedWith ( 'Ownable: caller is not the owner' ) ; } ) ; } ) ; } ) ; } ) ;