Disallow ERC20._transfer from the zero address. (#1752)
* Add requirement of non-zero from to ERC20 transfer.
* Add test for transferFrom zero address to behavior.
* Create ERC20.transfer behavior.
* Add tests for _transfer.
* Add changelog entry.
* Fix linter error.
* Delete repeated test.
* Fix hardcoded error prefix.
* Update CHANGELOG.md
Co-Authored-By: Francisco Giordano <frangio.1@gmail.com>
* Address review comments.
(cherry picked from commit ad18098d65)
This commit is contained in:
@ -23,151 +23,127 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip
|
||||
});
|
||||
|
||||
describe('transfer', function () {
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
const to = recipient;
|
||||
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transfer(to, amount, { from: initialHolder }),
|
||||
'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await this.token.transfer(to, amount, { from: initialHolder });
|
||||
|
||||
(await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
|
||||
|
||||
(await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
const { logs } = await this.token.transfer(to, amount, { from: initialHolder });
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: to,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
const to = ZERO_ADDRESS;
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transfer(to, initialSupply, { from: initialHolder }),
|
||||
`${errorPrefix}: transfer to the zero address`
|
||||
);
|
||||
});
|
||||
});
|
||||
shouldBehaveLikeERC20Transfer(errorPrefix, initialHolder, recipient, initialSupply,
|
||||
function (from, to, value) {
|
||||
return this.token.transfer(to, value, { from });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('transfer from', function () {
|
||||
const spender = recipient;
|
||||
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
const to = anotherAccount;
|
||||
describe('when the token owner is not the zero address', function () {
|
||||
const tokenOwner = initialHolder;
|
||||
|
||||
describe('when the spender has enough approved balance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply, { from: initialHolder });
|
||||
});
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
const to = anotherAccount;
|
||||
|
||||
describe('when the initial holder has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await this.token.transferFrom(initialHolder, to, amount, { from: spender });
|
||||
|
||||
(await this.token.balanceOf(initialHolder)).should.be.bignumber.equal('0');
|
||||
|
||||
(await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
|
||||
describe('when the spender has enough approved balance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply, { from: initialHolder });
|
||||
});
|
||||
|
||||
it('decreases the spender allowance', async function () {
|
||||
await this.token.transferFrom(initialHolder, to, amount, { from: spender });
|
||||
describe('when the token owner has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
(await this.token.allowance(initialHolder, spender)).should.be.bignumber.equal('0');
|
||||
});
|
||||
it('transfers the requested amount', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
|
||||
(await this.token.balanceOf(tokenOwner)).should.be.bignumber.equal('0');
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: to,
|
||||
value: amount,
|
||||
(await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('decreases the spender allowance', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
(await this.token.allowance(tokenOwner, spender)).should.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
const { logs } = await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from: tokenOwner,
|
||||
to: to,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
const { logs } = await this.token.transferFrom(tokenOwner, to, amount, { from: spender });
|
||||
|
||||
expectEvent.inLogs(logs, 'Approval', {
|
||||
owner: tokenOwner,
|
||||
spender: spender,
|
||||
value: await this.token.allowance(tokenOwner, spender),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
const { logs } = await this.token.transferFrom(initialHolder, to, amount, { from: spender });
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
expectEvent.inLogs(logs, 'Approval', {
|
||||
owner: initialHolder,
|
||||
spender: spender,
|
||||
value: await this.token.allowance(initialHolder, spender),
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the initial holder does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
describe('when the spender does not have enough approved balance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply.subn(1), { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
describe('when the token owner has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender does not have enough approved balance', function () {
|
||||
describe('when the recipient is the zero address', function () {
|
||||
const amount = initialSupply;
|
||||
const to = ZERO_ADDRESS;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply.subn(1), { from: initialHolder });
|
||||
await this.token.approve(spender, amount, { from: tokenOwner });
|
||||
});
|
||||
|
||||
describe('when the initial holder has enough balance', function () {
|
||||
const amount = initialSupply;
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the initial holder does not have enough balance', function () {
|
||||
const amount = initialSupply.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
const amount = initialSupply;
|
||||
const to = ZERO_ADDRESS;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, amount, { from: initialHolder });
|
||||
});
|
||||
describe('when the token owner is the zero address', function () {
|
||||
const amount = 0;
|
||||
const tokenOwner = ZERO_ADDRESS;
|
||||
const to = recipient;
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferFrom(
|
||||
initialHolder, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`
|
||||
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer from the zero address`
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -182,6 +158,72 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer) {
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const amount = balance.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(transfer.call(this, from, to, amount),
|
||||
'SafeMath: subtraction overflow'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender transfers all balance', function () {
|
||||
const amount = balance;
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await transfer.call(this, from, to, amount);
|
||||
|
||||
(await this.token.balanceOf(from)).should.be.bignumber.equal('0');
|
||||
|
||||
(await this.token.balanceOf(to)).should.be.bignumber.equal(amount);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
const { logs } = await transfer.call(this, from, to, amount);
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from,
|
||||
to,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender transfers zero tokens', function () {
|
||||
const amount = new BN('0');
|
||||
|
||||
it('transfers the requested amount', async function () {
|
||||
await transfer.call(this, from, to, amount);
|
||||
|
||||
(await this.token.balanceOf(from)).should.be.bignumber.equal(balance);
|
||||
|
||||
(await this.token.balanceOf(to)).should.be.bignumber.equal('0');
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
const { logs } = await transfer.call(this, from, to, amount);
|
||||
|
||||
expectEvent.inLogs(logs, 'Transfer', {
|
||||
from,
|
||||
to,
|
||||
value: amount,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(transfer.call(this, from, ZERO_ADDRESS, balance),
|
||||
`${errorPrefix}: transfer to the zero address`
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) {
|
||||
describe('when the spender is not the zero address', function () {
|
||||
describe('when the sender has enough balance', function () {
|
||||
@ -264,5 +306,6 @@ function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, appr
|
||||
|
||||
module.exports = {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Transfer,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ const { ZERO_ADDRESS } = constants;
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Transfer,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
} = require('./ERC20.behavior');
|
||||
|
||||
@ -330,6 +331,20 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_transfer', function () {
|
||||
shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) {
|
||||
return this.token.transferInternal(from, to, amount);
|
||||
});
|
||||
|
||||
describe('when the sender is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await shouldFail.reverting.withMessage(this.token.transferInternal(ZERO_ADDRESS, recipient, initialSupply),
|
||||
'ERC20: transfer from the zero address'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_approve', function () {
|
||||
shouldBehaveLikeERC20Approve('ERC20', initialHolder, recipient, initialSupply, function (owner, spender, amount) {
|
||||
return this.token.approveInternal(owner, spender, amount);
|
||||
|
||||
Reference in New Issue
Block a user