Add StandardBurnableToken implementation (#870)
* Add StandardBurnableToken implementation BurnableToken that extends from StandardToken and adds a burnFrom method that decrements allowance. Equivalent to a transferFrom plus burn in a single operation. * Return event object from expectEvent helper * Add comment on Approval event in burnFrom function * Improvements on burnable token tests - Inject initial balance as a parameter to the behaviour - Use expectEvent helper for assertions on events - Use chai bignumber for numbers - Change to bdd-style assertionspull/882/merge
parent
9e1c934ffd
commit
0926729c8f
@ -0,0 +1,13 @@ |
||||
pragma solidity ^0.4.18; |
||||
|
||||
import "../token/ERC20/StandardBurnableToken.sol"; |
||||
|
||||
|
||||
contract StandardBurnableTokenMock is StandardBurnableToken { |
||||
|
||||
function StandardBurnableTokenMock(address initialAccount, uint initialBalance) public { |
||||
balances[initialAccount] = initialBalance; |
||||
totalSupply_ = initialBalance; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@ |
||||
pragma solidity ^0.4.18; |
||||
|
||||
import "./BurnableToken.sol"; |
||||
import "./StandardToken.sol"; |
||||
|
||||
/** |
||||
* @title Standard Burnable Token |
||||
* @dev Adds burnFrom method to ERC20 implementations |
||||
*/ |
||||
contract StandardBurnableToken is BurnableToken, StandardToken { |
||||
|
||||
/** |
||||
* @dev Burns a specific amount of tokens from the target address and decrements allowance |
||||
* @param _from address The address which you want to send tokens from |
||||
* @param _value uint256 The amount of token to be burned |
||||
*/ |
||||
function burnFrom(address _from, uint256 _value) public { |
||||
require(_value <= allowed[_from][msg.sender]); |
||||
// Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, |
||||
// this function needs to emit an event with the updated approval. |
||||
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); |
||||
_burn(_from, _value); |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
import assertRevert from '../../helpers/assertRevert'; |
||||
import { inLogs } from '../../helpers/expectEvent'; |
||||
|
||||
const BigNumber = web3.BigNumber; |
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; |
||||
|
||||
require('chai') |
||||
.use(require('chai-as-promised')) |
||||
.use(require('chai-bignumber')(BigNumber)) |
||||
.should(); |
||||
|
||||
export default function ([owner], initialBalance) { |
||||
describe('as a basic burnable token', function () { |
||||
const from = owner; |
||||
|
||||
describe('when the given amount is not greater than balance of the sender', function () { |
||||
const amount = 100; |
||||
|
||||
beforeEach(async function () { |
||||
({ logs: this.logs } = await this.token.burn(amount, { from })); |
||||
}); |
||||
|
||||
it('burns the requested amount', async function () { |
||||
const balance = await this.token.balanceOf(from); |
||||
balance.should.be.bignumber.equal(initialBalance - amount); |
||||
}); |
||||
|
||||
it('emits a burn event', async function () { |
||||
const event = await inLogs(this.logs, 'Burn'); |
||||
event.args.burner.should.eq(owner); |
||||
event.args.value.should.be.bignumber.equal(amount); |
||||
}); |
||||
|
||||
it('emits a transfer event', async function () { |
||||
const event = await inLogs(this.logs, 'Transfer'); |
||||
event.args.from.should.eq(owner); |
||||
event.args.to.should.eq(ZERO_ADDRESS); |
||||
event.args.value.should.be.bignumber.equal(amount); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the given amount is greater than the balance of the sender', function () { |
||||
const amount = initialBalance + 1; |
||||
|
||||
it('reverts', async function () { |
||||
await assertRevert(this.token.burn(amount, { from })); |
||||
}); |
||||
}); |
||||
}); |
||||
}; |
@ -1,45 +1,12 @@ |
||||
import assertRevert from '../../helpers/assertRevert'; |
||||
import shouldBehaveLikeBurnableToken from './BurnableToken.behaviour'; |
||||
const BurnableTokenMock = artifacts.require('BurnableTokenMock'); |
||||
|
||||
contract('BurnableToken', function ([owner]) { |
||||
const initialBalance = 1000; |
||||
|
||||
beforeEach(async function () { |
||||
this.token = await BurnableTokenMock.new(owner, 1000); |
||||
this.token = await BurnableTokenMock.new(owner, initialBalance); |
||||
}); |
||||
|
||||
describe('burn', function () { |
||||
const from = owner; |
||||
|
||||
describe('when the given amount is not greater than balance of the sender', function () { |
||||
const amount = 100; |
||||
|
||||
it('burns the requested amount', async function () { |
||||
await this.token.burn(amount, { from }); |
||||
|
||||
const balance = await this.token.balanceOf(from); |
||||
assert.equal(balance, 900); |
||||
}); |
||||
|
||||
it('emits a burn event', async function () { |
||||
const { logs } = await this.token.burn(amount, { from }); |
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; |
||||
assert.equal(logs.length, 2); |
||||
assert.equal(logs[0].event, 'Burn'); |
||||
assert.equal(logs[0].args.burner, owner); |
||||
assert.equal(logs[0].args.value, amount); |
||||
|
||||
assert.equal(logs[1].event, 'Transfer'); |
||||
assert.equal(logs[1].args.from, owner); |
||||
assert.equal(logs[1].args.to, ZERO_ADDRESS); |
||||
assert.equal(logs[1].args.value, amount); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the given amount is greater than the balance of the sender', function () { |
||||
const amount = 1001; |
||||
|
||||
it('reverts', async function () { |
||||
await assertRevert(this.token.burn(amount, { from })); |
||||
}); |
||||
}); |
||||
}); |
||||
shouldBehaveLikeBurnableToken([owner], initialBalance); |
||||
}); |
||||
|
@ -0,0 +1,73 @@ |
||||
import assertRevert from '../../helpers/assertRevert'; |
||||
import { inLogs } from '../../helpers/expectEvent'; |
||||
import shouldBehaveLikeBurnableToken from './BurnableToken.behaviour'; |
||||
|
||||
const StandardBurnableTokenMock = artifacts.require('StandardBurnableTokenMock'); |
||||
const BigNumber = web3.BigNumber; |
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; |
||||
|
||||
require('chai') |
||||
.use(require('chai-as-promised')) |
||||
.use(require('chai-bignumber')(BigNumber)) |
||||
.should(); |
||||
|
||||
contract('StandardBurnableToken', function ([owner, burner]) { |
||||
const initialBalance = 1000; |
||||
|
||||
beforeEach(async function () { |
||||
this.token = await StandardBurnableTokenMock.new(owner, initialBalance); |
||||
}); |
||||
|
||||
shouldBehaveLikeBurnableToken([owner], initialBalance); |
||||
|
||||
describe('burnFrom', function () { |
||||
describe('on success', function () { |
||||
const amount = 100; |
||||
|
||||
beforeEach(async function () { |
||||
await this.token.approve(burner, 300, { from: owner }); |
||||
const { logs } = await this.token.burnFrom(owner, amount, { from: burner }); |
||||
this.logs = logs; |
||||
}); |
||||
|
||||
it('burns the requested amount', async function () { |
||||
const balance = await this.token.balanceOf(owner); |
||||
balance.should.be.bignumber.equal(initialBalance - amount); |
||||
}); |
||||
|
||||
it('decrements allowance', async function () { |
||||
const allowance = await this.token.allowance(owner, burner); |
||||
allowance.should.be.bignumber.equal(200); |
||||
}); |
||||
|
||||
it('emits a burn event', async function () { |
||||
const event = await inLogs(this.logs, 'Burn'); |
||||
event.args.burner.should.eq(owner); |
||||
event.args.value.should.be.bignumber.equal(amount); |
||||
}); |
||||
|
||||
it('emits a transfer event', async function () { |
||||
const event = await inLogs(this.logs, 'Transfer'); |
||||
event.args.from.should.eq(owner); |
||||
event.args.to.should.eq(ZERO_ADDRESS); |
||||
event.args.value.should.be.bignumber.equal(amount); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the given amount is greater than the balance of the sender', function () { |
||||
const amount = initialBalance + 1; |
||||
it('reverts', async function () { |
||||
await this.token.approve(burner, amount, { from: owner }); |
||||
await assertRevert(this.token.burnFrom(owner, amount, { from: burner })); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the given amount is greater than the allowance', function () { |
||||
const amount = 100; |
||||
it('reverts', async function () { |
||||
await this.token.approve(burner, amount - 1, { from: owner }); |
||||
await assertRevert(this.token.burnFrom(owner, amount, { from: burner })); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue