mirror of openzeppelin-contracts
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.
 
 
 
 
 
openzeppelin-contracts/test/token/ERC20/extensions/draft-ERC20TemporaryApprova...

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);
});
});
});