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.
142 lines
5.1 KiB
142 lines
5.1 KiB
const { ethers } = require('hardhat');
|
|
const { expect } = require('chai');
|
|
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
|
const { max, min } = require('../../../helpers/math.js');
|
|
|
|
const { shouldBehaveLikeERC20 } = require('../ERC20.behavior.js');
|
|
|
|
const name = 'My Token';
|
|
const symbol = 'MTKN';
|
|
const initialSupply = 100n;
|
|
|
|
async function fixture() {
|
|
// this.accounts is used by shouldBehaveLikeERC20
|
|
const accounts = await ethers.getSigners();
|
|
const [holder, recipient, other] = accounts;
|
|
|
|
const token = await ethers.deployContract('$ERC20TemporaryApproval', [name, symbol]);
|
|
await token.$_mint(holder, initialSupply);
|
|
|
|
const spender = await ethers.deployContract('$Address');
|
|
const batch = await ethers.deployContract('BatchCaller');
|
|
const getter = await ethers.deployContract('ERC20GetterHelper');
|
|
|
|
return { accounts, holder, recipient, other, token, spender, batch, getter };
|
|
}
|
|
|
|
describe('ERC20TemporaryApproval', function () {
|
|
beforeEach(async function () {
|
|
Object.assign(this, await loadFixture(fixture));
|
|
});
|
|
|
|
shouldBehaveLikeERC20(initialSupply);
|
|
|
|
describe('setting and spending temporary allowance', function () {
|
|
beforeEach(async function () {
|
|
await this.token.connect(this.holder).transfer(this.batch, initialSupply);
|
|
});
|
|
|
|
for (let {
|
|
description,
|
|
persistentAllowance,
|
|
temporaryAllowance,
|
|
amount,
|
|
temporaryExpected,
|
|
persistentExpected,
|
|
} of [
|
|
{ description: 'can set temporary allowance', temporaryAllowance: 42n },
|
|
{
|
|
description: 'can set temporary allowance on top of persistent allowance',
|
|
temporaryAllowance: 42n,
|
|
persistentAllowance: 17n,
|
|
},
|
|
{ description: 'support allowance overflow', temporaryAllowance: ethers.MaxUint256, persistentAllowance: 17n },
|
|
{ description: 'consuming temporary allowance alone', temporaryAllowance: 42n, amount: 2n },
|
|
{
|
|
description: 'fallback to persistent allowance if temporary allowance is not sufficient',
|
|
temporaryAllowance: 42n,
|
|
persistentAllowance: 17n,
|
|
amount: 50n,
|
|
},
|
|
{
|
|
description: 'do not reduce infinite temporary allowance #1',
|
|
temporaryAllowance: ethers.MaxUint256,
|
|
amount: 50n,
|
|
temporaryExpected: ethers.MaxUint256,
|
|
},
|
|
{
|
|
description: 'do not reduce infinite temporary allowance #2',
|
|
temporaryAllowance: 17n,
|
|
persistentAllowance: ethers.MaxUint256,
|
|
amount: 50n,
|
|
temporaryExpected: ethers.MaxUint256,
|
|
persistentExpected: ethers.MaxUint256,
|
|
},
|
|
]) {
|
|
persistentAllowance ??= 0n;
|
|
temporaryAllowance ??= 0n;
|
|
amount ??= 0n;
|
|
temporaryExpected ??= min(persistentAllowance + temporaryAllowance - amount, ethers.MaxUint256);
|
|
persistentExpected ??= persistentAllowance - max(amount - temporaryAllowance, 0n);
|
|
|
|
it(description, async function () {
|
|
await expect(
|
|
this.batch.execute(
|
|
[
|
|
persistentAllowance && {
|
|
target: this.token,
|
|
value: 0n,
|
|
data: this.token.interface.encodeFunctionData('approve', [this.spender.target, persistentAllowance]),
|
|
},
|
|
temporaryAllowance && {
|
|
target: this.token,
|
|
value: 0n,
|
|
data: this.token.interface.encodeFunctionData('temporaryApprove', [
|
|
this.spender.target,
|
|
temporaryAllowance,
|
|
]),
|
|
},
|
|
amount && {
|
|
target: this.spender,
|
|
value: 0n,
|
|
data: this.spender.interface.encodeFunctionData('$functionCall', [
|
|
this.token.target,
|
|
this.token.interface.encodeFunctionData('transferFrom', [
|
|
this.batch.target,
|
|
this.recipient.address,
|
|
amount,
|
|
]),
|
|
]),
|
|
},
|
|
{
|
|
target: this.getter,
|
|
value: 0n,
|
|
data: this.getter.interface.encodeFunctionData('allowance', [
|
|
this.token.target,
|
|
this.batch.target,
|
|
this.spender.target,
|
|
]),
|
|
},
|
|
].filter(Boolean),
|
|
),
|
|
)
|
|
.to.emit(this.getter, 'ERC20Allowance')
|
|
.withArgs(this.token, this.batch, this.spender, temporaryExpected);
|
|
|
|
expect(await this.token.allowance(this.batch, this.spender)).to.equal(persistentExpected);
|
|
});
|
|
}
|
|
|
|
it('reverts when the recipient is the zero address', async function () {
|
|
await expect(this.token.connect(this.holder).temporaryApprove(ethers.ZeroAddress, 1n))
|
|
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidSpender')
|
|
.withArgs(ethers.ZeroAddress);
|
|
});
|
|
|
|
it('reverts when the token owner is the zero address', async function () {
|
|
await expect(this.token.$_temporaryApprove(ethers.ZeroAddress, this.recipient, 1n))
|
|
.to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover')
|
|
.withArgs(ethers.ZeroAddress);
|
|
});
|
|
});
|
|
});
|
|
|