You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
7.5 KiB
202 lines
7.5 KiB
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
|
const { expect } = require('chai');
|
|
const { ZERO_ADDRESS } = constants;
|
|
|
|
const {
|
|
shouldBehaveLikeERC20,
|
|
shouldBehaveLikeERC20Transfer,
|
|
shouldBehaveLikeERC20Approve,
|
|
} = require('./ERC20.behavior');
|
|
const { expectRevertCustomError } = require('../../helpers/customError');
|
|
|
|
const TOKENS = [
|
|
{ Token: artifacts.require('$ERC20') },
|
|
{ Token: artifacts.require('$ERC20ApprovalMock'), forcedApproval: true },
|
|
];
|
|
|
|
contract('ERC20', function (accounts) {
|
|
const [initialHolder, recipient] = accounts;
|
|
|
|
const name = 'My Token';
|
|
const symbol = 'MTKN';
|
|
const initialSupply = new BN(100);
|
|
|
|
for (const { Token, forcedApproval } of TOKENS) {
|
|
describe(`using ${Token._json.contractName}`, function () {
|
|
beforeEach(async function () {
|
|
this.token = await Token.new(name, symbol);
|
|
await this.token.$_mint(initialHolder, initialSupply);
|
|
});
|
|
|
|
shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval });
|
|
|
|
it('has a name', async function () {
|
|
expect(await this.token.name()).to.equal(name);
|
|
});
|
|
|
|
it('has a symbol', async function () {
|
|
expect(await this.token.symbol()).to.equal(symbol);
|
|
});
|
|
|
|
it('has 18 decimals', async function () {
|
|
expect(await this.token.decimals()).to.be.bignumber.equal('18');
|
|
});
|
|
|
|
describe('_mint', function () {
|
|
const value = new BN(50);
|
|
it('rejects a null account', async function () {
|
|
await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]);
|
|
});
|
|
|
|
it('rejects overflow', async function () {
|
|
const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
|
|
await expectRevert(
|
|
this.token.$_mint(recipient, maxUint256),
|
|
'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)',
|
|
);
|
|
});
|
|
|
|
describe('for a non zero account', function () {
|
|
beforeEach('minting', async function () {
|
|
this.receipt = await this.token.$_mint(recipient, value);
|
|
});
|
|
|
|
it('increments totalSupply', async function () {
|
|
const expectedSupply = initialSupply.add(value);
|
|
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
|
});
|
|
|
|
it('increments recipient balance', async function () {
|
|
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value);
|
|
});
|
|
|
|
it('emits Transfer event', async function () {
|
|
const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient });
|
|
|
|
expect(event.args.value).to.be.bignumber.equal(value);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('_burn', function () {
|
|
it('rejects a null account', async function () {
|
|
await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [
|
|
ZERO_ADDRESS,
|
|
]);
|
|
});
|
|
|
|
describe('for a non zero account', function () {
|
|
it('rejects burning more than balance', async function () {
|
|
await expectRevertCustomError(
|
|
this.token.$_burn(initialHolder, initialSupply.addn(1)),
|
|
'ERC20InsufficientBalance',
|
|
[initialHolder, initialSupply, initialSupply.addn(1)],
|
|
);
|
|
});
|
|
|
|
const describeBurn = function (description, value) {
|
|
describe(description, function () {
|
|
beforeEach('burning', async function () {
|
|
this.receipt = await this.token.$_burn(initialHolder, value);
|
|
});
|
|
|
|
it('decrements totalSupply', async function () {
|
|
const expectedSupply = initialSupply.sub(value);
|
|
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
|
});
|
|
|
|
it('decrements initialHolder balance', async function () {
|
|
const expectedBalance = initialSupply.sub(value);
|
|
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance);
|
|
});
|
|
|
|
it('emits Transfer event', async function () {
|
|
const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS });
|
|
|
|
expect(event.args.value).to.be.bignumber.equal(value);
|
|
});
|
|
});
|
|
};
|
|
|
|
describeBurn('for entire balance', initialSupply);
|
|
describeBurn('for less value than balance', initialSupply.subn(1));
|
|
});
|
|
});
|
|
|
|
describe('_update', function () {
|
|
const value = new BN(1);
|
|
|
|
it('from is the zero address', async function () {
|
|
const balanceBefore = await this.token.balanceOf(initialHolder);
|
|
const totalSupply = await this.token.totalSupply();
|
|
|
|
expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, value), 'Transfer', {
|
|
from: ZERO_ADDRESS,
|
|
to: initialHolder,
|
|
value: value,
|
|
});
|
|
expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(value));
|
|
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(value));
|
|
});
|
|
|
|
it('to is the zero address', async function () {
|
|
const balanceBefore = await this.token.balanceOf(initialHolder);
|
|
const totalSupply = await this.token.totalSupply();
|
|
|
|
expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, value), 'Transfer', {
|
|
from: initialHolder,
|
|
to: ZERO_ADDRESS,
|
|
value: value,
|
|
});
|
|
expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(value));
|
|
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(value));
|
|
});
|
|
|
|
it('from and to are the zero address', async function () {
|
|
const totalSupply = await this.token.totalSupply();
|
|
|
|
await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value);
|
|
|
|
expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply);
|
|
expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value), 'Transfer', {
|
|
from: ZERO_ADDRESS,
|
|
to: ZERO_ADDRESS,
|
|
value: value,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('_transfer', function () {
|
|
shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) {
|
|
return this.token.$_transfer(from, to, value);
|
|
});
|
|
|
|
describe('when the sender is the zero address', function () {
|
|
it('reverts', async function () {
|
|
await expectRevertCustomError(
|
|
this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply),
|
|
'ERC20InvalidSender',
|
|
[ZERO_ADDRESS],
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('_approve', function () {
|
|
shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) {
|
|
return this.token.$_approve(owner, spender, value);
|
|
});
|
|
|
|
describe('when the owner is the zero address', function () {
|
|
it('reverts', async function () {
|
|
await expectRevertCustomError(
|
|
this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply),
|
|
'ERC20InvalidApprover',
|
|
[ZERO_ADDRESS],
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|