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