Migrate ERC20 and ERC20Wrapper tests to ethersjs (#4743)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
@ -1,335 +1,271 @@
|
||||
const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS, MAX_UINT256 } = constants;
|
||||
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) {
|
||||
const [initialHolder, recipient, anotherAccount] = accounts;
|
||||
function shouldBehaveLikeERC20(initialSupply, opts = {}) {
|
||||
const { forcedApproval } = opts;
|
||||
|
||||
describe('total supply', function () {
|
||||
it('returns the total token value', async function () {
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
it('total supply: returns the total token value', async function () {
|
||||
expect(await this.token.totalSupply()).to.equal(initialSupply);
|
||||
});
|
||||
|
||||
describe('balanceOf', function () {
|
||||
describe('when the requested account has no tokens', function () {
|
||||
it('returns zero', async function () {
|
||||
expect(await this.token.balanceOf(anotherAccount)).to.be.bignumber.equal('0');
|
||||
});
|
||||
it('returns zero when the requested account has no tokens', async function () {
|
||||
expect(await this.token.balanceOf(this.anotherAccount)).to.equal(0n);
|
||||
});
|
||||
|
||||
describe('when the requested account has some tokens', function () {
|
||||
it('returns the total token value', async function () {
|
||||
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(initialSupply);
|
||||
});
|
||||
it('returns the total token value when the requested account has some tokens', async function () {
|
||||
expect(await this.token.balanceOf(this.initialHolder)).to.equal(initialSupply);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transfer', function () {
|
||||
shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) {
|
||||
return this.token.transfer(to, value, { from });
|
||||
beforeEach(function () {
|
||||
this.transfer = (from, to, value) => this.token.connect(from).transfer(to, value);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20Transfer(initialSupply);
|
||||
});
|
||||
|
||||
describe('transfer from', function () {
|
||||
const spender = recipient;
|
||||
|
||||
describe('when the token owner is not the zero address', function () {
|
||||
const tokenOwner = initialHolder;
|
||||
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
const to = anotherAccount;
|
||||
|
||||
describe('when the spender has enough allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, initialSupply, { from: initialHolder });
|
||||
await this.token.connect(this.initialHolder).approve(this.recipient, initialSupply);
|
||||
});
|
||||
|
||||
describe('when the token owner has enough balance', function () {
|
||||
const value = initialSupply;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.tx = await this.token
|
||||
.connect(this.recipient)
|
||||
.transferFrom(this.initialHolder, this.anotherAccount, value);
|
||||
});
|
||||
|
||||
it('transfers the requested value', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, value, { from: spender });
|
||||
|
||||
expect(await this.token.balanceOf(tokenOwner)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value);
|
||||
await expect(this.tx).to.changeTokenBalances(
|
||||
this.token,
|
||||
[this.initialHolder, this.anotherAccount],
|
||||
[-value, value],
|
||||
);
|
||||
});
|
||||
|
||||
it('decreases the spender allowance', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, value, { from: spender });
|
||||
|
||||
expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal('0');
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(0n);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Transfer', {
|
||||
from: tokenOwner,
|
||||
to: to,
|
||||
value: value,
|
||||
});
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.anotherAccount.address, value);
|
||||
});
|
||||
|
||||
if (forcedApproval) {
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Approval', {
|
||||
owner: tokenOwner,
|
||||
spender: spender,
|
||||
value: await this.token.allowance(tokenOwner, spender),
|
||||
});
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Approval')
|
||||
.withArgs(
|
||||
this.initialHolder.address,
|
||||
this.recipient.address,
|
||||
await this.token.allowance(this.initialHolder, this.recipient),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
it('does not emit an approval event', async function () {
|
||||
expectEvent.notEmitted(
|
||||
await this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'Approval',
|
||||
);
|
||||
await expect(this.tx).to.not.emit(this.token, 'Approval');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
it('reverts when the token owner does not have enough balance', async function () {
|
||||
const value = initialSupply;
|
||||
|
||||
beforeEach('reducing balance', async function () {
|
||||
await this.token.transfer(to, 1, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'ERC20InsufficientBalance',
|
||||
[tokenOwner, value - 1, value],
|
||||
);
|
||||
});
|
||||
await this.token.connect(this.initialHolder).transfer(this.anotherAccount, 1n);
|
||||
await expect(
|
||||
this.token.connect(this.recipient).transferFrom(this.initialHolder, this.anotherAccount, value),
|
||||
)
|
||||
.to.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, value - 1n, value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender does not have enough allowance', function () {
|
||||
const allowance = initialSupply.subn(1);
|
||||
const allowance = initialSupply - 1n;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, allowance, { from: tokenOwner });
|
||||
await this.token.connect(this.initialHolder).approve(this.recipient, allowance);
|
||||
});
|
||||
|
||||
describe('when the token owner has enough balance', function () {
|
||||
it('reverts when the token owner has enough balance', async function () {
|
||||
const value = initialSupply;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'ERC20InsufficientAllowance',
|
||||
[spender, allowance, value],
|
||||
);
|
||||
});
|
||||
await expect(
|
||||
this.token.connect(this.recipient).transferFrom(this.initialHolder, this.anotherAccount, value),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance')
|
||||
.withArgs(this.recipient.address, allowance, value);
|
||||
});
|
||||
|
||||
describe('when the token owner does not have enough balance', function () {
|
||||
it('reverts when the token owner does not have enough balance', async function () {
|
||||
const value = allowance;
|
||||
|
||||
beforeEach('reducing balance', async function () {
|
||||
await this.token.transfer(to, 2, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'ERC20InsufficientBalance',
|
||||
[tokenOwner, value - 1, value],
|
||||
);
|
||||
});
|
||||
await this.token.connect(this.initialHolder).transfer(this.anotherAccount, 2);
|
||||
await expect(
|
||||
this.token.connect(this.recipient).transferFrom(this.initialHolder, this.anotherAccount, value),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, value - 1n, value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender has unlimited allowance', function () {
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, MAX_UINT256, { from: initialHolder });
|
||||
await this.token.connect(this.initialHolder).approve(this.recipient, ethers.MaxUint256);
|
||||
this.tx = await this.token
|
||||
.connect(this.recipient)
|
||||
.transferFrom(this.initialHolder, this.anotherAccount, 1n);
|
||||
});
|
||||
|
||||
it('does not decrease the spender allowance', async function () {
|
||||
await this.token.transferFrom(tokenOwner, to, 1, { from: spender });
|
||||
|
||||
expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal(MAX_UINT256);
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(ethers.MaxUint256);
|
||||
});
|
||||
|
||||
it('does not emit an approval event', async function () {
|
||||
expectEvent.notEmitted(await this.token.transferFrom(tokenOwner, to, 1, { from: spender }), 'Approval');
|
||||
await expect(this.tx).to.not.emit(this.token, 'Approval');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
it('reverts when the recipient is the zero address', async function () {
|
||||
const value = initialSupply;
|
||||
const to = ZERO_ADDRESS;
|
||||
|
||||
beforeEach(async function () {
|
||||
await this.token.approve(spender, value, { from: tokenOwner });
|
||||
});
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'ERC20InvalidReceiver',
|
||||
[ZERO_ADDRESS],
|
||||
);
|
||||
});
|
||||
await this.token.connect(this.initialHolder).approve(this.recipient, value);
|
||||
await expect(this.token.connect(this.recipient).transferFrom(this.initialHolder, ethers.ZeroAddress, value))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the token owner is the zero address', function () {
|
||||
const value = 0;
|
||||
const tokenOwner = ZERO_ADDRESS;
|
||||
const to = recipient;
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.transferFrom(tokenOwner, to, value, { from: spender }),
|
||||
'ERC20InvalidApprover',
|
||||
[ZERO_ADDRESS],
|
||||
);
|
||||
});
|
||||
it('reverts when the token owner is the zero address', async function () {
|
||||
const value = 0n;
|
||||
await expect(this.token.connect(this.recipient).transferFrom(ethers.ZeroAddress, this.recipient, value))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
});
|
||||
|
||||
describe('approve', function () {
|
||||
shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) {
|
||||
return this.token.approve(spender, value, { from: owner });
|
||||
beforeEach(function () {
|
||||
this.approve = (owner, spender, value) => this.token.connect(owner).approve(spender, value);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20Approve(initialSupply);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) {
|
||||
function shouldBehaveLikeERC20Transfer(balance) {
|
||||
describe('when the recipient is not the zero address', function () {
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const value = balance.addn(1);
|
||||
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(transfer.call(this, from, to, value), 'ERC20InsufficientBalance', [
|
||||
from,
|
||||
balance,
|
||||
value,
|
||||
]);
|
||||
});
|
||||
it('reverts when the sender does not have enough balance', async function () {
|
||||
const value = balance + 1n;
|
||||
await expect(this.transfer(this.initialHolder, this.recipient, value))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, balance, value);
|
||||
});
|
||||
|
||||
describe('when the sender transfers all balance', function () {
|
||||
const value = balance;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.tx = await this.transfer(this.initialHolder, this.recipient, value);
|
||||
});
|
||||
|
||||
it('transfers the requested value', async function () {
|
||||
await transfer.call(this, from, to, value);
|
||||
|
||||
expect(await this.token.balanceOf(from)).to.be.bignumber.equal('0');
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value);
|
||||
await expect(this.tx).to.changeTokenBalances(this.token, [this.initialHolder, this.recipient], [-value, value]);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value });
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.recipient.address, value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender transfers zero tokens', function () {
|
||||
const value = new BN('0');
|
||||
const value = 0n;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.tx = await this.transfer(this.initialHolder, this.recipient, value);
|
||||
});
|
||||
|
||||
it('transfers the requested value', async function () {
|
||||
await transfer.call(this, from, to, value);
|
||||
|
||||
expect(await this.token.balanceOf(from)).to.be.bignumber.equal(balance);
|
||||
|
||||
expect(await this.token.balanceOf(to)).to.be.bignumber.equal('0');
|
||||
await expect(this.tx).to.changeTokenBalances(this.token, [this.initialHolder, this.recipient], [0n, 0n]);
|
||||
});
|
||||
|
||||
it('emits a transfer event', async function () {
|
||||
expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value });
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.recipient.address, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the recipient is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(transfer.call(this, from, ZERO_ADDRESS, balance), 'ERC20InvalidReceiver', [
|
||||
ZERO_ADDRESS,
|
||||
]);
|
||||
});
|
||||
it('reverts when the recipient is the zero address', async function () {
|
||||
await expect(this.transfer(this.initialHolder, ethers.ZeroAddress, balance))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldBehaveLikeERC20Approve(owner, spender, supply, approve) {
|
||||
function shouldBehaveLikeERC20Approve(supply) {
|
||||
describe('when the spender is not the zero address', function () {
|
||||
describe('when the sender has enough balance', function () {
|
||||
const value = supply;
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(await approve.call(this, owner, spender, value), 'Approval', {
|
||||
owner: owner,
|
||||
spender: spender,
|
||||
value: value,
|
||||
});
|
||||
await expect(this.approve(this.initialHolder, this.recipient, value))
|
||||
.to.emit(this.token, 'Approval')
|
||||
.withArgs(this.initialHolder.address, this.recipient.address, value);
|
||||
});
|
||||
|
||||
describe('when there was no approved value before', function () {
|
||||
it('approves the requested value', async function () {
|
||||
await approve.call(this, owner, spender, value);
|
||||
it('approves the requested value when there was no approved value before', async function () {
|
||||
await this.approve(this.initialHolder, this.recipient, value);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
|
||||
});
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(value);
|
||||
});
|
||||
|
||||
describe('when the spender had an approved value', function () {
|
||||
beforeEach(async function () {
|
||||
await approve.call(this, owner, spender, new BN(1));
|
||||
});
|
||||
it('approves the requested value and replaces the previous one when the spender had an approved value', async function () {
|
||||
await this.approve(this.initialHolder, this.recipient, 1n);
|
||||
await this.approve(this.initialHolder, this.recipient, value);
|
||||
|
||||
it('approves the requested value and replaces the previous one', async function () {
|
||||
await approve.call(this, owner, spender, value);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
|
||||
});
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the sender does not have enough balance', function () {
|
||||
const value = supply.addn(1);
|
||||
const value = supply + 1n;
|
||||
|
||||
it('emits an approval event', async function () {
|
||||
expectEvent(await approve.call(this, owner, spender, value), 'Approval', {
|
||||
owner: owner,
|
||||
spender: spender,
|
||||
value: value,
|
||||
});
|
||||
await expect(this.approve(this.initialHolder, this.recipient, value))
|
||||
.to.emit(this.token, 'Approval')
|
||||
.withArgs(this.initialHolder.address, this.recipient.address, value);
|
||||
});
|
||||
|
||||
describe('when there was no approved value before', function () {
|
||||
it('approves the requested value', async function () {
|
||||
await approve.call(this, owner, spender, value);
|
||||
it('approves the requested value when there was no approved value before', async function () {
|
||||
await this.approve(this.initialHolder, this.recipient, value);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
|
||||
});
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(value);
|
||||
});
|
||||
|
||||
describe('when the spender had an approved value', function () {
|
||||
beforeEach(async function () {
|
||||
await approve.call(this, owner, spender, new BN(1));
|
||||
});
|
||||
it('approves the requested value and replaces the previous one when the spender had an approved value', async function () {
|
||||
await this.approve(this.initialHolder, this.recipient, 1n);
|
||||
await this.approve(this.initialHolder, this.recipient, value);
|
||||
|
||||
it('approves the requested value and replaces the previous one', async function () {
|
||||
await approve.call(this, owner, spender, value);
|
||||
|
||||
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
|
||||
});
|
||||
expect(await this.token.allowance(this.initialHolder, this.recipient)).to.equal(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the spender is the zero address', function () {
|
||||
it('reverts', async function () {
|
||||
await expectRevertCustomError(approve.call(this, owner, ZERO_ADDRESS, supply), `ERC20InvalidSpender`, [
|
||||
ZERO_ADDRESS,
|
||||
]);
|
||||
});
|
||||
it('reverts when the spender is the zero address', async function () {
|
||||
await expect(this.approve(this.initialHolder, ethers.ZeroAddress, supply))
|
||||
.to.be.revertedWithCustomError(this.token, `ERC20InvalidSpender`)
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +1,37 @@
|
||||
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS } = constants;
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeERC20,
|
||||
shouldBehaveLikeERC20Transfer,
|
||||
shouldBehaveLikeERC20Approve,
|
||||
} = require('./ERC20.behavior');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
const TOKENS = [
|
||||
{ Token: artifacts.require('$ERC20') },
|
||||
{ Token: artifacts.require('$ERC20ApprovalMock'), forcedApproval: true },
|
||||
];
|
||||
const TOKENS = [{ Token: '$ERC20' }, { Token: '$ERC20ApprovalMock', forcedApproval: true }];
|
||||
|
||||
contract('ERC20', function (accounts) {
|
||||
const [initialHolder, recipient] = accounts;
|
||||
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const initialSupply = new BN(100);
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const initialSupply = 100n;
|
||||
|
||||
describe('ERC20', function () {
|
||||
for (const { Token, forcedApproval } of TOKENS) {
|
||||
describe(`using ${Token._json.contractName}`, function () {
|
||||
describe(Token, function () {
|
||||
const fixture = async () => {
|
||||
const [initialHolder, recipient, anotherAccount] = await ethers.getSigners();
|
||||
|
||||
const token = await ethers.deployContract(Token, [name, symbol]);
|
||||
await token.$_mint(initialHolder, initialSupply);
|
||||
|
||||
return { initialHolder, recipient, anotherAccount, token };
|
||||
};
|
||||
|
||||
beforeEach(async function () {
|
||||
this.token = await Token.new(name, symbol);
|
||||
await this.token.$_mint(initialHolder, initialSupply);
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval });
|
||||
shouldBehaveLikeERC20(initialSupply, { forcedApproval });
|
||||
|
||||
it('has a name', async function () {
|
||||
expect(await this.token.name()).to.equal(name);
|
||||
@ -39,162 +42,164 @@ contract('ERC20', function (accounts) {
|
||||
});
|
||||
|
||||
it('has 18 decimals', async function () {
|
||||
expect(await this.token.decimals()).to.be.bignumber.equal('18');
|
||||
expect(await this.token.decimals()).to.equal(18n);
|
||||
});
|
||||
|
||||
describe('_mint', function () {
|
||||
const value = new BN(50);
|
||||
const value = 50n;
|
||||
it('rejects a null account', async function () {
|
||||
await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]);
|
||||
await expect(this.token.$_mint(ethers.ZeroAddress, value))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
|
||||
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)',
|
||||
await expect(this.token.$_mint(this.recipient, ethers.MaxUint256)).to.be.revertedWithPanic(
|
||||
PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW,
|
||||
);
|
||||
});
|
||||
|
||||
describe('for a non zero account', function () {
|
||||
beforeEach('minting', async function () {
|
||||
this.receipt = await this.token.$_mint(recipient, value);
|
||||
this.tx = await this.token.$_mint(this.recipient, value);
|
||||
});
|
||||
|
||||
it('increments totalSupply', async function () {
|
||||
const expectedSupply = initialSupply.add(value);
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
||||
await expect(await this.token.totalSupply()).to.equal(initialSupply + value);
|
||||
});
|
||||
|
||||
it('increments recipient balance', async function () {
|
||||
expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value);
|
||||
await expect(this.tx).to.changeTokenBalance(this.token, this.recipient, 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);
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(ethers.ZeroAddress, this.recipient.address, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_burn', function () {
|
||||
it('rejects a null account', async function () {
|
||||
await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [
|
||||
ZERO_ADDRESS,
|
||||
]);
|
||||
await expect(this.token.$_burn(ethers.ZeroAddress, 1n))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidSender')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
|
||||
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)],
|
||||
);
|
||||
await expect(this.token.$_burn(this.initialHolder, initialSupply + 1n))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, initialSupply, initialSupply + 1n);
|
||||
});
|
||||
|
||||
const describeBurn = function (description, value) {
|
||||
describe(description, function () {
|
||||
beforeEach('burning', async function () {
|
||||
this.receipt = await this.token.$_burn(initialHolder, value);
|
||||
this.tx = await this.token.$_burn(this.initialHolder, value);
|
||||
});
|
||||
|
||||
it('decrements totalSupply', async function () {
|
||||
const expectedSupply = initialSupply.sub(value);
|
||||
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
|
||||
expect(await this.token.totalSupply()).to.equal(initialSupply - value);
|
||||
});
|
||||
|
||||
it('decrements initialHolder balance', async function () {
|
||||
const expectedBalance = initialSupply.sub(value);
|
||||
expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance);
|
||||
await expect(this.tx).to.changeTokenBalance(this.token, this.initialHolder, -value);
|
||||
});
|
||||
|
||||
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);
|
||||
await expect(this.tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, ethers.ZeroAddress, value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describeBurn('for entire balance', initialSupply);
|
||||
describeBurn('for less value than balance', initialSupply.subn(1));
|
||||
describeBurn('for less value than balance', initialSupply - 1n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_update', function () {
|
||||
const value = new BN(1);
|
||||
const value = 1n;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.totalSupply = await this.token.totalSupply();
|
||||
});
|
||||
|
||||
it('from is the zero address', async function () {
|
||||
const balanceBefore = await this.token.balanceOf(initialHolder);
|
||||
const totalSupply = await this.token.totalSupply();
|
||||
const tx = await this.token.$_update(ethers.ZeroAddress, this.initialHolder, value);
|
||||
await expect(tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(ethers.ZeroAddress, this.initialHolder.address, value);
|
||||
|
||||
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));
|
||||
expect(await this.token.totalSupply()).to.equal(this.totalSupply + value);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, value);
|
||||
});
|
||||
|
||||
it('to is the zero address', async function () {
|
||||
const balanceBefore = await this.token.balanceOf(initialHolder);
|
||||
const totalSupply = await this.token.totalSupply();
|
||||
const tx = await this.token.$_update(this.initialHolder, ethers.ZeroAddress, value);
|
||||
await expect(tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, ethers.ZeroAddress, value);
|
||||
|
||||
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));
|
||||
expect(await this.token.totalSupply()).to.equal(this.totalSupply - value);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, -value);
|
||||
});
|
||||
|
||||
it('from and to are the zero address', async function () {
|
||||
const totalSupply = await this.token.totalSupply();
|
||||
describe('from and to are the same address', function () {
|
||||
it('zero address', async function () {
|
||||
const tx = await this.token.$_update(ethers.ZeroAddress, ethers.ZeroAddress, value);
|
||||
await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, ethers.ZeroAddress, value);
|
||||
|
||||
await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value);
|
||||
expect(await this.token.totalSupply()).to.equal(this.totalSupply);
|
||||
await expect(tx).to.changeTokenBalance(this.token, ethers.ZeroAddress, 0n);
|
||||
});
|
||||
|
||||
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('non zero address', function () {
|
||||
it('reverts without balance', async function () {
|
||||
await expect(this.token.$_update(this.recipient, this.recipient, value))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.recipient.address, 0n, value);
|
||||
});
|
||||
|
||||
it('executes with balance', async function () {
|
||||
const tx = await this.token.$_update(this.initialHolder, this.initialHolder, value);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, 0n);
|
||||
await expect(tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.initialHolder.address, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('_transfer', function () {
|
||||
shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) {
|
||||
return this.token.$_transfer(from, to, value);
|
||||
beforeEach(function () {
|
||||
this.transfer = this.token.$_transfer;
|
||||
});
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
shouldBehaveLikeERC20Transfer(initialSupply);
|
||||
|
||||
it('reverts when the sender is the zero address', async function () {
|
||||
await expect(this.token.$_transfer(ethers.ZeroAddress, this.recipient, initialSupply))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidSender')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_approve', function () {
|
||||
shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) {
|
||||
return this.token.$_approve(owner, spender, value);
|
||||
beforeEach(function () {
|
||||
this.approve = this.token.$_approve;
|
||||
});
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
shouldBehaveLikeERC20Approve(initialSupply);
|
||||
|
||||
it('reverts when the owner is the zero address', async function () {
|
||||
await expect(this.token.$_approve(ethers.ZeroAddress, this.recipient, initialSupply))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover')
|
||||
.withArgs(ethers.ZeroAddress);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,31 +1,34 @@
|
||||
const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { ZERO_ADDRESS, MAX_UINT256 } = constants;
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { shouldBehaveLikeERC20 } = require('../ERC20.behavior');
|
||||
const { expectRevertCustomError } = require('../../../helpers/customError');
|
||||
|
||||
const NotAnERC20 = artifacts.require('CallReceiverMock');
|
||||
const ERC20Decimals = artifacts.require('$ERC20DecimalsMock');
|
||||
const ERC20Wrapper = artifacts.require('$ERC20Wrapper');
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
const initialSupply = 100n;
|
||||
|
||||
contract('ERC20Wrapper', function (accounts) {
|
||||
const [initialHolder, receiver] = accounts;
|
||||
async function fixture() {
|
||||
const [initialHolder, recipient, anotherAccount] = await ethers.getSigners();
|
||||
|
||||
const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 9]);
|
||||
await underlying.$_mint(initialHolder, initialSupply);
|
||||
|
||||
const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, underlying]);
|
||||
|
||||
return { initialHolder, recipient, anotherAccount, underlying, token };
|
||||
}
|
||||
|
||||
describe('ERC20Wrapper', function () {
|
||||
const name = 'My Token';
|
||||
const symbol = 'MTKN';
|
||||
|
||||
const initialSupply = new BN(100);
|
||||
|
||||
beforeEach(async function () {
|
||||
this.underlying = await ERC20Decimals.new(name, symbol, 9);
|
||||
await this.underlying.$_mint(initialHolder, initialSupply);
|
||||
|
||||
this.token = await ERC20Wrapper.new(`Wrapped ${name}`, `W${symbol}`, this.underlying.address);
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
expect(await this.underlying.balanceOf(this.token.address)).to.be.bignumber.equal(await this.token.totalSupply());
|
||||
afterEach('Underlying balance', async function () {
|
||||
expect(await this.underlying.balanceOf(this.token)).to.be.equal(await this.token.totalSupply());
|
||||
});
|
||||
|
||||
it('has a name', async function () {
|
||||
@ -37,175 +40,169 @@ contract('ERC20Wrapper', function (accounts) {
|
||||
});
|
||||
|
||||
it('has the same decimals as the underlying token', async function () {
|
||||
expect(await this.token.decimals()).to.be.bignumber.equal('9');
|
||||
expect(await this.token.decimals()).to.be.equal(9n);
|
||||
});
|
||||
|
||||
it('decimals default back to 18 if token has no metadata', async function () {
|
||||
const noDecimals = await NotAnERC20.new();
|
||||
const otherToken = await ERC20Wrapper.new(`Wrapped ${name}`, `W${symbol}`, noDecimals.address);
|
||||
expect(await otherToken.decimals()).to.be.bignumber.equal('18');
|
||||
const noDecimals = await ethers.deployContract('CallReceiverMock');
|
||||
const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, noDecimals]);
|
||||
expect(await token.decimals()).to.be.equal(18n);
|
||||
});
|
||||
|
||||
it('has underlying', async function () {
|
||||
expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address);
|
||||
expect(await this.token.underlying()).to.be.equal(this.underlying.target);
|
||||
});
|
||||
|
||||
describe('deposit', function () {
|
||||
it('valid', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
const { tx } = await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: this.token.address,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: initialHolder,
|
||||
value: initialSupply,
|
||||
});
|
||||
});
|
||||
it('executes with approval', async function () {
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
|
||||
const tx = await this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply);
|
||||
await expect(tx)
|
||||
.to.emit(this.underlying, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.token.target, initialSupply)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(ethers.ZeroAddress, this.initialHolder.address, initialSupply);
|
||||
|
||||
it('missing approval', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }),
|
||||
'ERC20InsufficientAllowance',
|
||||
[this.token.address, 0, initialSupply],
|
||||
await expect(tx).to.changeTokenBalances(
|
||||
this.underlying,
|
||||
[this.initialHolder, this.token],
|
||||
[-initialSupply, initialSupply],
|
||||
);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
it('missing balance', async function () {
|
||||
await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder });
|
||||
await expectRevertCustomError(
|
||||
this.token.depositFor(initialHolder, MAX_UINT256, { from: initialHolder }),
|
||||
'ERC20InsufficientBalance',
|
||||
[initialHolder, initialSupply, MAX_UINT256],
|
||||
it('reverts when missing approval', async function () {
|
||||
await expect(this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply))
|
||||
.to.be.revertedWithCustomError(this.underlying, 'ERC20InsufficientAllowance')
|
||||
.withArgs(this.token.target, 0, initialSupply);
|
||||
});
|
||||
|
||||
it('reverts when inssuficient balance', async function () {
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, ethers.MaxUint256);
|
||||
await expect(this.token.connect(this.initialHolder).depositFor(this.initialHolder, ethers.MaxUint256))
|
||||
.to.be.revertedWithCustomError(this.underlying, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, initialSupply, ethers.MaxUint256);
|
||||
});
|
||||
|
||||
it('deposits to other account', async function () {
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
|
||||
const tx = await this.token.connect(this.initialHolder).depositFor(this.recipient, initialSupply);
|
||||
await expect(tx)
|
||||
.to.emit(this.underlying, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, this.token.target, initialSupply)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(ethers.ZeroAddress, this.recipient.address, initialSupply);
|
||||
|
||||
await expect(tx).to.changeTokenBalances(
|
||||
this.underlying,
|
||||
[this.initialHolder, this.token],
|
||||
[-initialSupply, initialSupply],
|
||||
);
|
||||
});
|
||||
|
||||
it('to other account', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
const { tx } = await this.token.depositFor(receiver, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: this.token.address,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: receiver,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expect(tx).to.changeTokenBalances(this.token, [this.initialHolder, this.recipient], [0, initialSupply]);
|
||||
});
|
||||
|
||||
it('reverts minting to the wrapper contract', async function () {
|
||||
await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder });
|
||||
await expectRevertCustomError(
|
||||
this.token.depositFor(this.token.address, MAX_UINT256, { from: initialHolder }),
|
||||
'ERC20InvalidReceiver',
|
||||
[this.token.address],
|
||||
);
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, ethers.MaxUint256);
|
||||
await expect(this.token.connect(this.initialHolder).depositFor(this.token, ethers.MaxUint256))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
|
||||
.withArgs(this.token.target);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdraw', function () {
|
||||
beforeEach(async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
|
||||
await this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
it('missing balance', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }),
|
||||
'ERC20InsufficientBalance',
|
||||
[initialHolder, initialSupply, MAX_UINT256],
|
||||
);
|
||||
it('reverts when inssuficient balance', async function () {
|
||||
await expect(this.token.connect(this.initialHolder).withdrawTo(this.initialHolder, ethers.MaxInt256))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
|
||||
.withArgs(this.initialHolder.address, initialSupply, ethers.MaxInt256);
|
||||
});
|
||||
|
||||
it('valid', async function () {
|
||||
const value = new BN(42);
|
||||
it('executes when operation is valid', async function () {
|
||||
const value = 42n;
|
||||
|
||||
const { tx } = await this.token.withdrawTo(initialHolder, value, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: initialHolder,
|
||||
value: value,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: value,
|
||||
});
|
||||
const tx = await this.token.connect(this.initialHolder).withdrawTo(this.initialHolder, value);
|
||||
await expect(tx)
|
||||
.to.emit(this.underlying, 'Transfer')
|
||||
.withArgs(this.token.target, this.initialHolder.address, value)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, ethers.ZeroAddress, value);
|
||||
|
||||
await expect(tx).to.changeTokenBalances(this.underlying, [this.token, this.initialHolder], [-value, value]);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, -value);
|
||||
});
|
||||
|
||||
it('entire balance', async function () {
|
||||
const { tx } = await this.token.withdrawTo(initialHolder, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: initialHolder,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: initialSupply,
|
||||
});
|
||||
const tx = await this.token.connect(this.initialHolder).withdrawTo(this.initialHolder, initialSupply);
|
||||
await expect(tx)
|
||||
.to.emit(this.underlying, 'Transfer')
|
||||
.withArgs(this.token.target, this.initialHolder.address, initialSupply)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, ethers.ZeroAddress, initialSupply);
|
||||
|
||||
await expect(tx).to.changeTokenBalances(
|
||||
this.underlying,
|
||||
[this.token, this.initialHolder],
|
||||
[-initialSupply, initialSupply],
|
||||
);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, -initialSupply);
|
||||
});
|
||||
|
||||
it('to other account', async function () {
|
||||
const { tx } = await this.token.withdrawTo(receiver, initialSupply, { from: initialHolder });
|
||||
await expectEvent.inTransaction(tx, this.underlying, 'Transfer', {
|
||||
from: this.token.address,
|
||||
to: receiver,
|
||||
value: initialSupply,
|
||||
});
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: initialHolder,
|
||||
to: ZERO_ADDRESS,
|
||||
value: initialSupply,
|
||||
});
|
||||
const tx = await this.token.connect(this.initialHolder).withdrawTo(this.recipient, initialSupply);
|
||||
await expect(tx)
|
||||
.to.emit(this.underlying, 'Transfer')
|
||||
.withArgs(this.token.target, this.recipient.address, initialSupply)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(this.initialHolder.address, ethers.ZeroAddress, initialSupply);
|
||||
|
||||
await expect(tx).to.changeTokenBalances(
|
||||
this.underlying,
|
||||
[this.token, this.initialHolder, this.recipient],
|
||||
[-initialSupply, 0, initialSupply],
|
||||
);
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.initialHolder, -initialSupply);
|
||||
});
|
||||
|
||||
it('reverts withdrawing to the wrapper contract', async function () {
|
||||
expectRevertCustomError(
|
||||
this.token.withdrawTo(this.token.address, initialSupply, { from: initialHolder }),
|
||||
'ERC20InvalidReceiver',
|
||||
[this.token.address],
|
||||
);
|
||||
await expect(this.token.connect(this.initialHolder).withdrawTo(this.token, initialSupply))
|
||||
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver')
|
||||
.withArgs(this.token.target);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recover', function () {
|
||||
it('nothing to recover', async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
|
||||
await this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply);
|
||||
|
||||
const { tx } = await this.token.$_recover(receiver);
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: receiver,
|
||||
value: '0',
|
||||
});
|
||||
const tx = await this.token.$_recover(this.recipient);
|
||||
await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.recipient.address, 0n);
|
||||
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.recipient, 0);
|
||||
});
|
||||
|
||||
it('something to recover', async function () {
|
||||
await this.underlying.transfer(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.underlying.connect(this.initialHolder).transfer(this.token, initialSupply);
|
||||
|
||||
const { tx } = await this.token.$_recover(receiver);
|
||||
await expectEvent.inTransaction(tx, this.token, 'Transfer', {
|
||||
from: ZERO_ADDRESS,
|
||||
to: receiver,
|
||||
value: initialSupply,
|
||||
});
|
||||
const tx = await this.token.$_recover(this.recipient);
|
||||
await expect(tx)
|
||||
.to.emit(this.token, 'Transfer')
|
||||
.withArgs(ethers.ZeroAddress, this.recipient.address, initialSupply);
|
||||
|
||||
await expect(tx).to.changeTokenBalance(this.token, this.recipient, initialSupply);
|
||||
});
|
||||
});
|
||||
|
||||
describe('erc20 behaviour', function () {
|
||||
beforeEach(async function () {
|
||||
await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder });
|
||||
await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder });
|
||||
await this.underlying.connect(this.initialHolder).approve(this.token, initialSupply);
|
||||
await this.token.connect(this.initialHolder).depositFor(this.initialHolder, initialSupply);
|
||||
});
|
||||
|
||||
shouldBehaveLikeERC20(initialSupply, accounts);
|
||||
shouldBehaveLikeERC20(initialSupply);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user