diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index 71e80d737..b277d8c12 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -4,11 +4,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const Enums = require('../helpers/enums'); -const { - getDomain, - domainType, - types: { Ballot }, -} = require('../helpers/eip712'); +const { getDomain, domainType, Ballot } = require('../helpers/eip712'); const { GovernorHelper, proposalStatesToBitMap } = require('../helpers/governance'); const { clockFromReceipt } = require('../helpers/time'); const { expectRevertCustomError } = require('../helpers/customError'); diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js index da392b3ea..bbac688a2 100644 --- a/test/governance/extensions/GovernorWithParams.test.js +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -4,11 +4,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const Enums = require('../../helpers/enums'); -const { - getDomain, - domainType, - types: { ExtendedBallot }, -} = require('../../helpers/eip712'); +const { getDomain, domainType, ExtendedBallot } = require('../../helpers/eip712'); const { GovernorHelper } = require('../../helpers/governance'); const { expectRevertCustomError } = require('../../helpers/customError'); diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 68445f0fb..6243cf4e4 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -7,11 +7,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { shouldBehaveLikeERC6372 } = require('./ERC6372.behavior'); -const { - getDomain, - domainType, - types: { Delegation }, -} = require('../../helpers/eip712'); +const { getDomain, domainType, Delegation } = require('../../helpers/eip712'); const { clockFromReceipt } = require('../../helpers/time'); const { expectRevertCustomError } = require('../../helpers/customError'); diff --git a/test/helpers/eip712-types.js b/test/helpers/eip712-types.js index 8aacf5325..b2b6ccf83 100644 --- a/test/helpers/eip712-types.js +++ b/test/helpers/eip712-types.js @@ -1,44 +1,52 @@ -module.exports = { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - { name: 'salt', type: 'bytes32' }, - ], - Permit: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint256' }, - ], - Ballot: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'support', type: 'uint8' }, - { name: 'voter', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - ], - ExtendedBallot: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'support', type: 'uint8' }, - { name: 'voter', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - { name: 'reason', type: 'string' }, - { name: 'params', type: 'bytes' }, - ], - Delegation: [ - { name: 'delegatee', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - { name: 'expiry', type: 'uint256' }, - ], - ForwardRequest: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'gas', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint48' }, - { name: 'data', type: 'bytes' }, - ], -}; +const { mapValues } = require('./iterate'); + +const formatType = schema => Object.entries(schema).map(([name, type]) => ({ name, type })); + +module.exports = mapValues( + { + EIP712Domain: { + name: 'string', + version: 'string', + chainId: 'uint256', + verifyingContract: 'address', + salt: 'bytes32', + }, + Permit: { + owner: 'address', + spender: 'address', + value: 'uint256', + nonce: 'uint256', + deadline: 'uint256', + }, + Ballot: { + proposalId: 'uint256', + support: 'uint8', + voter: 'address', + nonce: 'uint256', + }, + ExtendedBallot: { + proposalId: 'uint256', + support: 'uint8', + voter: 'address', + nonce: 'uint256', + reason: 'string', + params: 'bytes', + }, + Delegation: { + delegatee: 'address', + nonce: 'uint256', + expiry: 'uint256', + }, + ForwardRequest: { + from: 'address', + to: 'address', + value: 'uint256', + gas: 'uint256', + nonce: 'uint256', + deadline: 'uint48', + data: 'bytes', + }, + }, + formatType, +); +module.exports.formatType = formatType; diff --git a/test/helpers/eip712.js b/test/helpers/eip712.js index 295c1b953..278e86cce 100644 --- a/test/helpers/eip712.js +++ b/test/helpers/eip712.js @@ -38,9 +38,9 @@ function hashTypedData(domain, structHash) { } module.exports = { - types, getDomain, domainType, domainSeparator: ethers.TypedDataEncoder.hashDomain, hashTypedData, + ...types, }; diff --git a/test/helpers/time.js b/test/helpers/time.js index 1f83a4aa2..874713ee5 100644 --- a/test/helpers/time.js +++ b/test/helpers/time.js @@ -1,6 +1,5 @@ const { time, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers'); - -const mapObject = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value)])); +const { mapValues } = require('./iterate'); module.exports = { clock: { @@ -22,8 +21,8 @@ module.exports = { // TODO: deprecate the old version in favor of this one module.exports.bigint = { - clock: mapObject(module.exports.clock, fn => () => fn().then(BigInt)), - clockFromReceipt: mapObject(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(BigInt)), + clock: mapValues(module.exports.clock, fn => () => fn().then(BigInt)), + clockFromReceipt: mapValues(module.exports.clockFromReceipt, fn => receipt => fn(receipt).then(BigInt)), forward: module.exports.forward, - duration: mapObject(module.exports.duration, fn => n => BigInt(fn(n))), + duration: mapValues(module.exports.duration, fn => n => BigInt(fn(n))), }; diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index 1ae48e6f2..07c4ff335 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -3,7 +3,7 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { impersonate } = require('../helpers/account'); -const { getDomain } = require('../helpers/eip712'); +const { getDomain, ForwardRequest } = require('../helpers/eip712'); const { MAX_UINT48 } = require('../helpers/constants'); const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); @@ -15,17 +15,7 @@ async function fixture() { const forwarderAsSigner = await impersonate(forwarder.target); const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]); const domain = await getDomain(forwarder); - const types = { - ForwardRequest: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'gas', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint48' }, - { name: 'data', type: 'bytes' }, - ], - }; + const types = { ForwardRequest }; return { sender, other, forwarder, forwarderAsSigner, context, domain, types }; } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index e0d1090c4..3bf264530 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -2,7 +2,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { getDomain } = require('../helpers/eip712'); +const { getDomain, ForwardRequest } = require('../helpers/eip712'); const { bigint: time } = require('../helpers/time'); const { sum } = require('../helpers/math'); @@ -12,17 +12,7 @@ async function fixture() { const forwarder = await ethers.deployContract('ERC2771Forwarder', ['ERC2771Forwarder']); const receiver = await ethers.deployContract('CallReceiverMockTrustingForwarder', [forwarder]); const domain = await getDomain(forwarder); - const types = { - ForwardRequest: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'gas', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint48' }, - { name: 'data', type: 'bytes' }, - ], - }; + const types = { ForwardRequest }; const forgeRequest = async (override = {}, signer = sender) => { const req = { diff --git a/test/token/ERC20/extensions/ERC20Burnable.behavior.js b/test/token/ERC20/extensions/ERC20Burnable.behavior.js deleted file mode 100644 index 937491bdf..000000000 --- a/test/token/ERC20/extensions/ERC20Burnable.behavior.js +++ /dev/null @@ -1,116 +0,0 @@ -const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); -const { expectRevertCustomError } = require('../../../helpers/customError'); - -function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { - describe('burn', function () { - describe('when the given value is not greater than balance of the sender', function () { - context('for a zero value', function () { - shouldBurn(new BN(0)); - }); - - context('for a non-zero value', function () { - shouldBurn(new BN(100)); - }); - - function shouldBurn(value) { - beforeEach(async function () { - this.receipt = await this.token.burn(value, { from: owner }); - }); - - it('burns the requested value', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value)); - }); - - it('emits a transfer event', async function () { - expectEvent(this.receipt, 'Transfer', { - from: owner, - to: ZERO_ADDRESS, - value: value, - }); - }); - } - }); - - describe('when the given value is greater than the balance of the sender', function () { - const value = initialBalance.addn(1); - - it('reverts', async function () { - await expectRevertCustomError(this.token.burn(value, { from: owner }), 'ERC20InsufficientBalance', [ - owner, - initialBalance, - value, - ]); - }); - }); - }); - - describe('burnFrom', function () { - describe('on success', function () { - context('for a zero value', function () { - shouldBurnFrom(new BN(0)); - }); - - context('for a non-zero value', function () { - shouldBurnFrom(new BN(100)); - }); - - function shouldBurnFrom(value) { - const originalAllowance = value.muln(3); - - beforeEach(async function () { - await this.token.approve(burner, originalAllowance, { from: owner }); - this.receipt = await this.token.burnFrom(owner, value, { from: burner }); - }); - - it('burns the requested value', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value)); - }); - - it('decrements allowance', async function () { - expect(await this.token.allowance(owner, burner)).to.be.bignumber.equal(originalAllowance.sub(value)); - }); - - it('emits a transfer event', async function () { - expectEvent(this.receipt, 'Transfer', { - from: owner, - to: ZERO_ADDRESS, - value: value, - }); - }); - } - }); - - describe('when the given value is greater than the balance of the sender', function () { - const value = initialBalance.addn(1); - - it('reverts', async function () { - await this.token.approve(burner, value, { from: owner }); - await expectRevertCustomError(this.token.burnFrom(owner, value, { from: burner }), 'ERC20InsufficientBalance', [ - owner, - initialBalance, - value, - ]); - }); - }); - - describe('when the given value is greater than the allowance', function () { - const allowance = new BN(100); - - it('reverts', async function () { - await this.token.approve(burner, allowance, { from: owner }); - await expectRevertCustomError( - this.token.burnFrom(owner, allowance.addn(1), { from: burner }), - 'ERC20InsufficientAllowance', - [burner, allowance, allowance.addn(1)], - ); - }); - }); - }); -} - -module.exports = { - shouldBehaveLikeERC20Burnable, -}; diff --git a/test/token/ERC20/extensions/ERC20Burnable.test.js b/test/token/ERC20/extensions/ERC20Burnable.test.js index 00acc81ed..8253acbf1 100644 --- a/test/token/ERC20/extensions/ERC20Burnable.test.js +++ b/test/token/ERC20/extensions/ERC20Burnable.test.js @@ -1,20 +1,108 @@ -const { BN } = require('@openzeppelin/test-helpers'); +const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { shouldBehaveLikeERC20Burnable } = require('./ERC20Burnable.behavior'); -const ERC20Burnable = artifacts.require('$ERC20Burnable'); +const name = 'My Token'; +const symbol = 'MTKN'; +const initialBalance = 1000n; -contract('ERC20Burnable', function (accounts) { - const [owner, ...otherAccounts] = accounts; +async function fixture() { + const [owner, burner] = await ethers.getSigners(); - const initialBalance = new BN(1000); + const token = await ethers.deployContract('$ERC20Burnable', [name, symbol], owner); + await token.$_mint(owner, initialBalance); - const name = 'My Token'; - const symbol = 'MTKN'; + return { owner, burner, token, initialBalance }; +} +describe('ERC20Burnable', function () { beforeEach(async function () { - this.token = await ERC20Burnable.new(name, symbol, { from: owner }); - await this.token.$_mint(owner, initialBalance); + Object.assign(this, await loadFixture(fixture)); }); - shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts); + describe('burn', function () { + it('reverts if not enough balance', async function () { + const value = this.initialBalance + 1n; + + await expect(this.token.connect(this.owner).burn(value)) + .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') + .withArgs(this.owner.address, this.initialBalance, value); + }); + + describe('on success', function () { + for (const { title, value } of [ + { title: 'for a zero value', value: 0n }, + { title: 'for a non-zero value', value: 100n }, + ]) { + describe(title, function () { + beforeEach(async function () { + this.tx = await this.token.connect(this.owner).burn(value); + }); + + it('burns the requested value', async function () { + await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value); + }); + + it('emits a transfer event', async function () { + await expect(this.tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.owner.address, ethers.ZeroAddress, value); + }); + }); + } + }); + }); + + describe('burnFrom', function () { + describe('reverts', function () { + it('if not enough balance', async function () { + const value = this.initialBalance + 1n; + + await this.token.connect(this.owner).approve(this.burner, value); + + await expect(this.token.connect(this.burner).burnFrom(this.owner, value)) + .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') + .withArgs(this.owner.address, this.initialBalance, value); + }); + + it('if not enough allowance', async function () { + const allowance = 100n; + + await this.token.connect(this.owner).approve(this.burner, allowance); + + await expect(this.token.connect(this.burner).burnFrom(this.owner, allowance + 1n)) + .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance') + .withArgs(this.burner.address, allowance, allowance + 1n); + }); + }); + + describe('on success', function () { + for (const { title, value } of [ + { title: 'for a zero value', value: 0n }, + { title: 'for a non-zero value', value: 100n }, + ]) { + describe(title, function () { + const originalAllowance = value * 3n; + + beforeEach(async function () { + await this.token.connect(this.owner).approve(this.burner, originalAllowance); + this.tx = await this.token.connect(this.burner).burnFrom(this.owner, value); + }); + + it('burns the requested value', async function () { + await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value); + }); + + it('decrements allowance', async function () { + expect(await this.token.allowance(this.owner, this.burner)).to.equal(originalAllowance - value); + }); + + it('emits a transfer event', async function () { + await expect(this.tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.owner.address, ethers.ZeroAddress, value); + }); + }); + } + }); + }); }); diff --git a/test/token/ERC20/extensions/ERC20Capped.behavior.js b/test/token/ERC20/extensions/ERC20Capped.behavior.js deleted file mode 100644 index 5af5c3ddc..000000000 --- a/test/token/ERC20/extensions/ERC20Capped.behavior.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const { expectRevertCustomError } = require('../../../helpers/customError'); - -function shouldBehaveLikeERC20Capped(accounts, cap) { - describe('capped token', function () { - const user = accounts[0]; - - it('starts with the correct cap', async function () { - expect(await this.token.cap()).to.be.bignumber.equal(cap); - }); - - it('mints when value is less than cap', async function () { - await this.token.$_mint(user, cap.subn(1)); - expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1)); - }); - - it('fails to mint if the value exceeds the cap', async function () { - await this.token.$_mint(user, cap.subn(1)); - await expectRevertCustomError(this.token.$_mint(user, 2), 'ERC20ExceededCap', [cap.addn(1), cap]); - }); - - it('fails to mint after cap is reached', async function () { - await this.token.$_mint(user, cap); - await expectRevertCustomError(this.token.$_mint(user, 1), 'ERC20ExceededCap', [cap.addn(1), cap]); - }); - }); -} - -module.exports = { - shouldBehaveLikeERC20Capped, -}; diff --git a/test/token/ERC20/extensions/ERC20Capped.test.js b/test/token/ERC20/extensions/ERC20Capped.test.js index 1f4a2bee3..a32ec43a8 100644 --- a/test/token/ERC20/extensions/ERC20Capped.test.js +++ b/test/token/ERC20/extensions/ERC20Capped.test.js @@ -1,24 +1,55 @@ -const { ether } = require('@openzeppelin/test-helpers'); -const { shouldBehaveLikeERC20Capped } = require('./ERC20Capped.behavior'); -const { expectRevertCustomError } = require('../../../helpers/customError'); +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const ERC20Capped = artifacts.require('$ERC20Capped'); +const name = 'My Token'; +const symbol = 'MTKN'; +const cap = 1000n; -contract('ERC20Capped', function (accounts) { - const cap = ether('1000'); +async function fixture() { + const [user] = await ethers.getSigners(); - const name = 'My Token'; - const symbol = 'MTKN'; + const token = await ethers.deployContract('$ERC20Capped', [name, symbol, cap]); + + return { user, token, cap }; +} + +describe('ERC20Capped', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); it('requires a non-zero cap', async function () { - await expectRevertCustomError(ERC20Capped.new(name, symbol, 0), 'ERC20InvalidCap', [0]); + const ERC20Capped = await ethers.getContractFactory('$ERC20Capped'); + + await expect(ERC20Capped.deploy(name, symbol, 0)) + .to.be.revertedWithCustomError(ERC20Capped, 'ERC20InvalidCap') + .withArgs(0); }); - context('once deployed', async function () { - beforeEach(async function () { - this.token = await ERC20Capped.new(name, symbol, cap); + describe('capped token', function () { + it('starts with the correct cap', async function () { + expect(await this.token.cap()).to.equal(this.cap); }); - shouldBehaveLikeERC20Capped(accounts, cap); + it('mints when value is less than cap', async function () { + const value = this.cap - 1n; + await this.token.$_mint(this.user, value); + expect(await this.token.totalSupply()).to.equal(value); + }); + + it('fails to mint if the value exceeds the cap', async function () { + await this.token.$_mint(this.user, this.cap - 1n); + await expect(this.token.$_mint(this.user, 2)) + .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap') + .withArgs(this.cap + 1n, this.cap); + }); + + it('fails to mint after cap is reached', async function () { + await this.token.$_mint(this.user, this.cap); + await expect(this.token.$_mint(this.user, 1)) + .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap') + .withArgs(this.cap + 1n, this.cap); + }); }); }); diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index 13d5b3ef4..cee00db0f 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -1,209 +1,163 @@ -/* eslint-disable */ - -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { expectRevertCustomError } = require('../../../helpers/customError'); -const { MAX_UINT256, ZERO_ADDRESS } = constants; +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const ERC20FlashMintMock = artifacts.require('$ERC20FlashMintMock'); -const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); +const name = 'My Token'; +const symbol = 'MTKN'; +const initialSupply = 100n; +const loanValue = 10_000_000_000_000n; -contract('ERC20FlashMint', function (accounts) { - const [initialHolder, other, anotherAccount] = accounts; +async function fixture() { + const [initialHolder, other, anotherAccount] = await ethers.getSigners(); - const name = 'My Token'; - const symbol = 'MTKN'; + const token = await ethers.deployContract('$ERC20FlashMintMock', [name, symbol]); + await token.$_mint(initialHolder, initialSupply); - const initialSupply = new BN(100); - const loanValue = new BN(10000000000000); + return { initialHolder, other, anotherAccount, token }; +} +describe('ERC20FlashMint', function () { beforeEach(async function () { - this.token = await ERC20FlashMintMock.new(name, symbol); - await this.token.$_mint(initialHolder, initialSupply); + Object.assign(this, await loadFixture(fixture)); }); describe('maxFlashLoan', function () { it('token match', async function () { - expect(await this.token.maxFlashLoan(this.token.address)).to.be.bignumber.equal(MAX_UINT256.sub(initialSupply)); + expect(await this.token.maxFlashLoan(this.token)).to.equal(ethers.MaxUint256 - initialSupply); }); it('token mismatch', async function () { - expect(await this.token.maxFlashLoan(ZERO_ADDRESS)).to.be.bignumber.equal('0'); + expect(await this.token.maxFlashLoan(ethers.ZeroAddress)).to.equal(0n); }); }); describe('flashFee', function () { it('token match', async function () { - expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal('0'); + expect(await this.token.flashFee(this.token, loanValue)).to.equal(0n); }); it('token mismatch', async function () { - await expectRevertCustomError(this.token.flashFee(ZERO_ADDRESS, loanValue), 'ERC3156UnsupportedToken', [ - ZERO_ADDRESS, - ]); + await expect(this.token.flashFee(ethers.ZeroAddress, loanValue)) + .to.be.revertedWithCustomError(this.token, 'ERC3156UnsupportedToken') + .withArgs(ethers.ZeroAddress); }); }); describe('flashFeeReceiver', function () { it('default receiver', async function () { - expect(await this.token.$_flashFeeReceiver()).to.be.eq(ZERO_ADDRESS); + expect(await this.token.$_flashFeeReceiver()).to.equal(ethers.ZeroAddress); }); }); describe('flashLoan', function () { it('success', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'); + const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: ZERO_ADDRESS, - to: receiver.address, - value: loanValue, - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: receiver.address, - to: ZERO_ADDRESS, - value: loanValue, - }); - await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { - token: this.token.address, - account: receiver.address, - value: loanValue, - }); - await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { - token: this.token.address, - value: initialSupply.add(loanValue), - }); + const tx = await this.token.flashLoan(receiver, this.token, loanValue, '0x'); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(ethers.ZeroAddress, receiver.target, loanValue) + .to.emit(this.token, 'Transfer') + .withArgs(receiver.target, ethers.ZeroAddress, loanValue) + .to.emit(receiver, 'BalanceOf') + .withArgs(this.token.target, receiver.target, loanValue) + .to.emit(receiver, 'TotalSupply') + .withArgs(this.token.target, initialSupply + loanValue); + await expect(tx).to.changeTokenBalance(this.token, receiver, 0); - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); - expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal('0'); - expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + expect(await this.token.totalSupply()).to.equal(initialSupply); + expect(await this.token.allowance(receiver, this.token)).to.equal(0n); }); it('missing return value', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(false, true); - await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), - 'ERC3156InvalidReceiver', - [receiver.address], - ); + const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [false, true]); + await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x')) + .to.be.revertedWithCustomError(this.token, 'ERC3156InvalidReceiver') + .withArgs(receiver.target); }); it('missing approval', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, false); - await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), - 'ERC20InsufficientAllowance', - [this.token.address, 0, loanValue], - ); + const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, false]); + await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x')) + .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance') + .withArgs(this.token.target, 0, loanValue); }); it('unavailable funds', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const data = this.token.contract.methods.transfer(other, 10).encodeABI(); - await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanValue, data), - 'ERC20InsufficientBalance', - [receiver.address, loanValue - 10, loanValue], - ); + const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); + const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]); + await expect(this.token.flashLoan(receiver, this.token, loanValue, data)) + .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') + .withArgs(receiver.target, loanValue - 10n, loanValue); }); it('more than maxFlashLoan', async function () { - const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const data = this.token.contract.methods.transfer(other, 10).encodeABI(); - // _mint overflow reverts using a panic code. No reason string. - await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data)); + const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); + const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]); + await expect(this.token.flashLoan(receiver, this.token, ethers.MaxUint256, data)) + .to.be.revertedWithCustomError(this.token, 'ERC3156ExceededMaxLoan') + .withArgs(ethers.MaxUint256 - initialSupply); }); describe('custom flash fee & custom fee receiver', function () { - const receiverInitialBalance = new BN(200000); - const flashFee = new BN(5000); + const receiverInitialBalance = 200_000n; + const flashFee = 5_000n; beforeEach('init receiver balance & set flash fee', async function () { - this.receiver = await ERC3156FlashBorrowerMock.new(true, true); - const receipt = await this.token.$_mint(this.receiver.address, receiverInitialBalance); - await expectEvent(receipt, 'Transfer', { - from: ZERO_ADDRESS, - to: this.receiver.address, - value: receiverInitialBalance, - }); - expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance); + this.receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); + + const tx = await this.token.$_mint(this.receiver, receiverInitialBalance); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(ethers.ZeroAddress, this.receiver.target, receiverInitialBalance); + await expect(tx).to.changeTokenBalance(this.token, this.receiver, receiverInitialBalance); await this.token.setFlashFee(flashFee); - expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal(flashFee); + expect(await this.token.flashFee(this.token, loanValue)).to.equal(flashFee); }); it('default flash fee receiver', async function () { - const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: ZERO_ADDRESS, - to: this.receiver.address, - value: loanValue, - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.receiver.address, - to: ZERO_ADDRESS, - value: loanValue.add(flashFee), - }); - await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { - token: this.token.address, - account: this.receiver.address, - value: receiverInitialBalance.add(loanValue), - }); - await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { - token: this.token.address, - value: initialSupply.add(receiverInitialBalance).add(loanValue), - }); + const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x'); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(ethers.ZeroAddress, this.receiver.target, loanValue) + .to.emit(this.token, 'Transfer') + .withArgs(this.receiver.target, ethers.ZeroAddress, loanValue + flashFee) + .to.emit(this.receiver, 'BalanceOf') + .withArgs(this.token.target, this.receiver.target, receiverInitialBalance + loanValue) + .to.emit(this.receiver, 'TotalSupply') + .withArgs(this.token.target, initialSupply + receiverInitialBalance + loanValue); + await expect(tx).to.changeTokenBalances(this.token, [this.receiver, ethers.ZeroAddress], [-flashFee, 0]); - expect(await this.token.totalSupply()).to.be.bignumber.equal( - initialSupply.add(receiverInitialBalance).sub(flashFee), - ); - expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal( - receiverInitialBalance.sub(flashFee), - ); - expect(await this.token.balanceOf(await this.token.$_flashFeeReceiver())).to.be.bignumber.equal('0'); - expect(await this.token.allowance(this.receiver.address, this.token.address)).to.be.bignumber.equal('0'); + expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance - flashFee); + expect(await this.token.allowance(this.receiver, this.token)).to.equal(0n); }); it('custom flash fee receiver', async function () { - const flashFeeReceiverAddress = anotherAccount; + const flashFeeReceiverAddress = this.anotherAccount; await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); - expect(await this.token.$_flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); + expect(await this.token.$_flashFeeReceiver()).to.equal(flashFeeReceiverAddress.address); - expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); - - const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x'); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: ZERO_ADDRESS, - to: this.receiver.address, - value: loanValue, - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.receiver.address, - to: ZERO_ADDRESS, - value: loanValue, - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.receiver.address, - to: flashFeeReceiverAddress, - value: flashFee, - }); - await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { - token: this.token.address, - account: this.receiver.address, - value: receiverInitialBalance.add(loanValue), - }); - await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { - token: this.token.address, - value: initialSupply.add(receiverInitialBalance).add(loanValue), - }); - - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); - expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal( - receiverInitialBalance.sub(flashFee), + const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x'); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(ethers.ZeroAddress, this.receiver.target, loanValue) + .to.emit(this.token, 'Transfer') + .withArgs(this.receiver.target, ethers.ZeroAddress, loanValue) + .to.emit(this.token, 'Transfer') + .withArgs(this.receiver.target, flashFeeReceiverAddress.address, flashFee) + .to.emit(this.receiver, 'BalanceOf') + .withArgs(this.token.target, this.receiver.target, receiverInitialBalance + loanValue) + .to.emit(this.receiver, 'TotalSupply') + .withArgs(this.token.target, initialSupply + receiverInitialBalance + loanValue); + await expect(tx).to.changeTokenBalances( + this.token, + [this.receiver, flashFeeReceiverAddress], + [-flashFee, flashFee], ); - expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); - expect(await this.token.allowance(this.receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + + expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance); + expect(await this.token.allowance(this.receiver, flashFeeReceiverAddress)).to.equal(0n); }); }); }); diff --git a/test/token/ERC20/extensions/ERC20Pausable.test.js b/test/token/ERC20/extensions/ERC20Pausable.test.js index 92c90b9b8..1f1157c19 100644 --- a/test/token/ERC20/extensions/ERC20Pausable.test.js +++ b/test/token/ERC20/extensions/ERC20Pausable.test.js @@ -1,135 +1,128 @@ -const { BN } = require('@openzeppelin/test-helpers'); - +const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { expectRevertCustomError } = require('../../../helpers/customError'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const ERC20Pausable = artifacts.require('$ERC20Pausable'); +const name = 'My Token'; +const symbol = 'MTKN'; +const initialSupply = 100n; -contract('ERC20Pausable', function (accounts) { - const [holder, recipient, anotherAccount] = accounts; +async function fixture() { + const [holder, recipient, approved] = await ethers.getSigners(); - const initialSupply = new BN(100); + const token = await ethers.deployContract('$ERC20Pausable', [name, symbol]); + await token.$_mint(holder, initialSupply); - const name = 'My Token'; - const symbol = 'MTKN'; + return { holder, recipient, approved, token }; +} +describe('ERC20Pausable', function () { beforeEach(async function () { - this.token = await ERC20Pausable.new(name, symbol); - await this.token.$_mint(holder, initialSupply); + Object.assign(this, await loadFixture(fixture)); }); describe('pausable token', function () { describe('transfer', function () { it('allows to transfer when unpaused', async function () { - await this.token.transfer(recipient, initialSupply, { from: holder }); - - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply); + await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances( + this.token, + [this.holder, this.recipient], + [-initialSupply, initialSupply], + ); }); it('allows to transfer when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.transfer(recipient, initialSupply, { from: holder }); - - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply); + await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances( + this.token, + [this.holder, this.recipient], + [-initialSupply, initialSupply], + ); }); it('reverts when trying to transfer when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError( - this.token.transfer(recipient, initialSupply, { from: holder }), - 'EnforcedPause', - [], - ); + await expect( + this.token.connect(this.holder).transfer(this.recipient, initialSupply), + ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); }); }); describe('transfer from', function () { - const allowance = new BN(40); + const allowance = 40n; beforeEach(async function () { - await this.token.approve(anotherAccount, allowance, { from: holder }); + await this.token.connect(this.holder).approve(this.approved, allowance); }); it('allows to transfer from when unpaused', async function () { - await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }); - - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance); - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance)); + await expect( + this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), + ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]); }); it('allows to transfer when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }); - - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance); - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance)); + await expect( + this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), + ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]); }); it('reverts when trying to transfer from when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError( - this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }), - 'EnforcedPause', - [], - ); + await expect( + this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), + ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); }); }); describe('mint', function () { - const value = new BN('42'); + const value = 42n; it('allows to mint when unpaused', async function () { - await this.token.$_mint(recipient, value); - - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); + await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value); }); it('allows to mint when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.$_mint(recipient, value); - - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); + await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value); }); it('reverts when trying to mint when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError(this.token.$_mint(recipient, value), 'EnforcedPause', []); + await expect(this.token.$_mint(this.recipient, value)).to.be.revertedWithCustomError( + this.token, + 'EnforcedPause', + ); }); }); describe('burn', function () { - const value = new BN('42'); + const value = 42n; it('allows to burn when unpaused', async function () { - await this.token.$_burn(holder, value); - - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value)); + await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value); }); it('allows to burn when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.$_burn(holder, value); - - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value)); + await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value); }); it('reverts when trying to burn when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError(this.token.$_burn(holder, value), 'EnforcedPause', []); + await expect(this.token.$_burn(this.holder, value)).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); }); }); }); diff --git a/test/token/ERC20/extensions/ERC20Permit.test.js b/test/token/ERC20/extensions/ERC20Permit.test.js index db2363cd2..e27a98239 100644 --- a/test/token/ERC20/extensions/ERC20Permit.test.js +++ b/test/token/ERC20/extensions/ERC20Permit.test.js @@ -1,41 +1,38 @@ -/* eslint-disable */ - -const { BN, constants, time } = require('@openzeppelin/test-helpers'); +const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { MAX_UINT256 } = constants; - -const { fromRpcSig } = require('ethereumjs-util'); -const ethSigUtil = require('eth-sig-util'); -const Wallet = require('ethereumjs-wallet').default; - -const ERC20Permit = artifacts.require('$ERC20Permit'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { getDomain, domainSeparator, Permit } = require('../../../helpers/eip712'); const { - types: { Permit }, - getDomain, - domainType, - domainSeparator, -} = require('../../../helpers/eip712'); -const { getChainId } = require('../../../helpers/chainid'); -const { expectRevertCustomError } = require('../../../helpers/customError'); + bigint: { clock, duration }, +} = require('../../../helpers/time'); -contract('ERC20Permit', function (accounts) { - const [initialHolder, spender] = accounts; +const name = 'My Token'; +const symbol = 'MTKN'; +const initialSupply = 100n; - const name = 'My Token'; - const symbol = 'MTKN'; +async function fixture() { + const [initialHolder, spender, owner, other] = await ethers.getSigners(); - const initialSupply = new BN(100); + const token = await ethers.deployContract('$ERC20Permit', [name, symbol, name]); + await token.$_mint(initialHolder, initialSupply); + return { + initialHolder, + spender, + owner, + other, + token, + }; +} + +describe('ERC20Permit', function () { beforeEach(async function () { - this.chainId = await getChainId(); - - this.token = await ERC20Permit.new(name, symbol, name); - await this.token.$_mint(initialHolder, initialSupply); + Object.assign(this, await loadFixture(fixture)); }); it('initial nonce is 0', async function () { - expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0'); + expect(await this.token.nonces(this.initialHolder)).to.equal(0n); }); it('domain separator', async function () { @@ -43,81 +40,72 @@ contract('ERC20Permit', function (accounts) { }); describe('permit', function () { - const wallet = Wallet.generate(); + const value = 42n; + const nonce = 0n; + const maxDeadline = ethers.MaxUint256; - const owner = wallet.getAddressString(); - const value = new BN(42); - const nonce = 0; - const maxDeadline = MAX_UINT256; - - const buildData = (contract, deadline = maxDeadline) => - getDomain(contract).then(domain => ({ - primaryType: 'Permit', - types: { EIP712Domain: domainType(domain), Permit }, - domain, - message: { owner, spender, value, nonce, deadline }, - })); + beforeEach(function () { + this.buildData = (contract, deadline = maxDeadline) => + getDomain(contract).then(domain => ({ + domain, + types: { Permit }, + message: { + owner: this.owner.address, + spender: this.spender.address, + value, + nonce, + deadline, + }, + })); + }); it('accepts owner signature', async function () { - const { v, r, s } = await buildData(this.token) - .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) - .then(fromRpcSig); + const { v, r, s } = await this.buildData(this.token) + .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) + .then(ethers.Signature.from); - await this.token.permit(owner, spender, value, maxDeadline, v, r, s); + await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s); - expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); + expect(await this.token.nonces(this.owner)).to.equal(1n); + expect(await this.token.allowance(this.owner, this.spender)).to.equal(value); }); it('rejects reused signature', async function () { - const sig = await buildData(this.token).then(data => - ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }), + const { v, r, s, serialized } = await this.buildData(this.token) + .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) + .then(ethers.Signature.from); + + await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s); + + const recovered = await this.buildData(this.token).then(({ domain, types, message }) => + ethers.verifyTypedData(domain, types, { ...message, nonce: nonce + 1n, deadline: maxDeadline }, serialized), ); - const { r, s, v } = fromRpcSig(sig); - await this.token.permit(owner, spender, value, maxDeadline, v, r, s); - - const domain = await getDomain(this.token); - const typedMessage = { - primaryType: 'Permit', - types: { EIP712Domain: domainType(domain), Permit }, - domain, - message: { owner, spender, value, nonce: nonce + 1, deadline: maxDeadline }, - }; - - await expectRevertCustomError( - this.token.permit(owner, spender, value, maxDeadline, v, r, s), - 'ERC2612InvalidSigner', - [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), owner], - ); + await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s)) + .to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner') + .withArgs(recovered, this.owner.address); }); it('rejects other signature', async function () { - const otherWallet = Wallet.generate(); + const { v, r, s } = await this.buildData(this.token) + .then(({ domain, types, message }) => this.other.signTypedData(domain, types, message)) + .then(ethers.Signature.from); - const { v, r, s } = await buildData(this.token) - .then(data => ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data })) - .then(fromRpcSig); - - await expectRevertCustomError( - this.token.permit(owner, spender, value, maxDeadline, v, r, s), - 'ERC2612InvalidSigner', - [await otherWallet.getAddressString(), owner], - ); + await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s)) + .to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner') + .withArgs(this.other.address, this.owner.address); }); it('rejects expired permit', async function () { - const deadline = (await time.latest()) - time.duration.weeks(1); + const deadline = (await clock.timestamp()) - duration.weeks(1); - const { v, r, s } = await buildData(this.token, deadline) - .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) - .then(fromRpcSig); + const { v, r, s } = await this.buildData(this.token, deadline) + .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) + .then(ethers.Signature.from); - await expectRevertCustomError( - this.token.permit(owner, spender, value, deadline, v, r, s), - 'ERC2612ExpiredSignature', - [deadline], - ); + await expect(this.token.permit(this.owner, this.spender, value, deadline, v, r, s)) + .to.be.revertedWithCustomError(this.token, 'ERC2612ExpiredSignature') + .withArgs(deadline); }); }); }); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index a0da162a4..9ec1c09e9 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -10,11 +10,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { batchInBlock } = require('../../../helpers/txpool'); -const { - getDomain, - domainType, - types: { Delegation }, -} = require('../../../helpers/eip712'); +const { getDomain, domainType, Delegation } = require('../../../helpers/eip712'); const { clock, clockFromReceipt } = require('../../../helpers/time'); const { expectRevertCustomError } = require('../../../helpers/customError'); diff --git a/test/token/ERC20/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js index b61573edd..af746d65a 100644 --- a/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -6,12 +6,13 @@ const { shouldBehaveLikeERC20 } = require('../ERC20.behavior'); const name = 'My Token'; const symbol = 'MTKN'; +const decimals = 9n; const initialSupply = 100n; async function fixture() { const [initialHolder, recipient, anotherAccount] = await ethers.getSigners(); - const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 9]); + const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); await underlying.$_mint(initialHolder, initialSupply); const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, underlying]); @@ -20,9 +21,6 @@ async function fixture() { } describe('ERC20Wrapper', function () { - const name = 'My Token'; - const symbol = 'MTKN'; - beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); }); @@ -40,7 +38,7 @@ describe('ERC20Wrapper', function () { }); it('has the same decimals as the underlying token', async function () { - expect(await this.token.decimals()).to.be.equal(9n); + expect(await this.token.decimals()).to.be.equal(decimals); }); it('decimals default back to 18 if token has no metadata', async function () { @@ -56,13 +54,13 @@ describe('ERC20Wrapper', function () { describe('deposit', function () { 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); - await expect(tx).to.changeTokenBalances( this.underlying, [this.initialHolder, this.token], @@ -79,6 +77,7 @@ describe('ERC20Wrapper', function () { 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); @@ -86,13 +85,13 @@ describe('ERC20Wrapper', function () { 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], @@ -103,6 +102,7 @@ describe('ERC20Wrapper', function () { it('reverts minting to the wrapper contract', async function () { 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); @@ -130,7 +130,6 @@ describe('ERC20Wrapper', function () { .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); }); @@ -142,7 +141,6 @@ describe('ERC20Wrapper', function () { .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], @@ -158,7 +156,6 @@ describe('ERC20Wrapper', function () { .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], @@ -181,7 +178,6 @@ describe('ERC20Wrapper', function () { 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); }); @@ -192,7 +188,6 @@ describe('ERC20Wrapper', function () { 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); }); }); diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index fa66785f0..907855efe 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -1,72 +1,71 @@ -const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { ethers } = require('hardhat'); const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); -const { Enum } = require('../../../helpers/enums'); -const { expectRevertCustomError } = require('../../../helpers/customError'); +const { + bigint: { Enum }, +} = require('../../../helpers/enums'); -const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); -const ERC4626 = artifacts.require('$ERC4626'); -const ERC4626LimitsMock = artifacts.require('$ERC4626LimitsMock'); -const ERC4626OffsetMock = artifacts.require('$ERC4626OffsetMock'); -const ERC4626FeesMock = artifacts.require('$ERC4626FeesMock'); -const ERC20ExcessDecimalsMock = artifacts.require('ERC20ExcessDecimalsMock'); -const ERC20Reentrant = artifacts.require('$ERC20Reentrant'); +const name = 'My Token'; +const symbol = 'MTKN'; +const decimals = 18n; -contract('ERC4626', function (accounts) { - const [holder, recipient, spender, other, user1, user2] = accounts; +async function fixture() { + const [holder, recipient, spender, other, ...accounts] = await ethers.getSigners(); + return { holder, recipient, spender, other, accounts }; +} - const name = 'My Token'; - const symbol = 'MTKN'; - const decimals = web3.utils.toBN(18); +describe('ERC4626', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); it('inherit decimals if from asset', async function () { - for (const decimals of [0, 9, 12, 18, 36].map(web3.utils.toBN)) { - const token = await ERC20Decimals.new('', '', decimals); - const vault = await ERC4626.new('', '', token.address); - expect(await vault.decimals()).to.be.bignumber.equal(decimals); + for (const decimals of [0n, 9n, 12n, 18n, 36n]) { + const token = await ethers.deployContract('$ERC20DecimalsMock', ['', '', decimals]); + const vault = await ethers.deployContract('$ERC4626', ['', '', token]); + expect(await vault.decimals()).to.equal(decimals); } }); it('asset has not yet been created', async function () { - const vault = await ERC4626.new('', '', other); - expect(await vault.decimals()).to.be.bignumber.equal(decimals); + const vault = await ethers.deployContract('$ERC4626', ['', '', this.other.address]); + expect(await vault.decimals()).to.equal(decimals); }); it('underlying excess decimals', async function () { - const token = await ERC20ExcessDecimalsMock.new(); - const vault = await ERC4626.new('', '', token.address); - expect(await vault.decimals()).to.be.bignumber.equal(decimals); + const token = await ethers.deployContract('$ERC20ExcessDecimalsMock'); + const vault = await ethers.deployContract('$ERC4626', ['', '', token]); + expect(await vault.decimals()).to.equal(decimals); }); it('decimals overflow', async function () { - for (const offset of [243, 250, 255].map(web3.utils.toBN)) { - const token = await ERC20Decimals.new('', '', decimals); - const vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', token.address, offset); - await expectRevert( - vault.decimals(), - 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', - ); + for (const offset of [243n, 250n, 255n]) { + const token = await ethers.deployContract('$ERC20DecimalsMock', ['', '', decimals]); + const vault = await ethers.deployContract('$ERC4626OffsetMock', ['', '', token, offset]); + await expect(vault.decimals()).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW); } }); describe('reentrancy', async function () { const reenterType = Enum('No', 'Before', 'After'); - const value = web3.utils.toBN(1000000000000000000); - const reenterValue = web3.utils.toBN(1000000000); - let token; - let vault; + const value = 1_000_000_000_000_000_000n; + const reenterValue = 1_000_000_000n; beforeEach(async function () { - token = await ERC20Reentrant.new(); // Use offset 1 so the rate is not 1:1 and we can't possibly confuse assets and shares - vault = await ERC4626OffsetMock.new('', '', token.address, 1); + const token = await ethers.deployContract('$ERC20Reentrant'); + const vault = await ethers.deployContract('$ERC4626OffsetMock', ['', '', token, 1n]); // Funds and approval for tests - await token.$_mint(holder, value); - await token.$_mint(other, value); - await token.$_approve(holder, vault.address, constants.MAX_UINT256); - await token.$_approve(other, vault.address, constants.MAX_UINT256); - await token.$_approve(token.address, vault.address, constants.MAX_UINT256); + await token.$_mint(this.holder, value); + await token.$_mint(this.other, value); + await token.$_approve(this.holder, vault, ethers.MaxUint256); + await token.$_approve(this.other, vault, ethers.MaxUint256); + await token.$_approve(token, vault, ethers.MaxUint256); + + Object.assign(this, { token, vault }); }); // During a `_deposit`, the vault does `transferFrom(depositor, vault, assets)` -> `_mint(receiver, shares)` @@ -75,40 +74,29 @@ contract('ERC4626', function (accounts) { // intermediate state in which the ratio of assets/shares has been decreased (more shares than assets). it('correct share price is observed during reentrancy before deposit', async function () { // mint token for deposit - await token.$_mint(token.address, reenterValue); + await this.token.$_mint(this.token, reenterValue); // Schedules a reentrancy from the token contract - await token.scheduleReenter( + await this.token.scheduleReenter( reenterType.Before, - vault.address, - vault.contract.methods.deposit(reenterValue, holder).encodeABI(), + this.vault, + this.vault.interface.encodeFunctionData('deposit', [reenterValue, this.holder.address]), ); // Initial share price - const sharesForDeposit = await vault.previewDeposit(value, { from: holder }); - const sharesForReenter = await vault.previewDeposit(reenterValue, { from: holder }); + const sharesForDeposit = await this.vault.previewDeposit(value); + const sharesForReenter = await this.vault.previewDeposit(reenterValue); - // Deposit normally, reentering before the internal `_update` - const receipt = await vault.deposit(value, holder, { from: holder }); - - // Main deposit event - await expectEvent(receipt, 'Deposit', { - sender: holder, - owner: holder, - assets: value, - shares: sharesForDeposit, - }); - // Reentrant deposit event → uses the same price - await expectEvent(receipt, 'Deposit', { - sender: token.address, - owner: holder, - assets: reenterValue, - shares: sharesForReenter, - }); + await expect(this.vault.connect(this.holder).deposit(value, this.holder)) + // Deposit normally, reentering before the internal `_update` + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.holder.address, value, sharesForDeposit) + // Reentrant deposit event → uses the same price + .to.emit(this.vault, 'Deposit') + .withArgs(this.token.target, this.holder.address, reenterValue, sharesForReenter); // Assert prices is kept - const sharesAfter = await vault.previewDeposit(value, { from: holder }); - expect(sharesForDeposit).to.be.bignumber.eq(sharesAfter); + expect(await this.vault.previewDeposit(value)).to.equal(sharesForDeposit); }); // During a `_withdraw`, the vault does `_burn(owner, shares)` -> `transfer(receiver, assets)` @@ -117,43 +105,31 @@ contract('ERC4626', function (accounts) { // intermediate state in which the ratio of shares/assets has been decreased (more assets than shares). it('correct share price is observed during reentrancy after withdraw', async function () { // Deposit into the vault: holder gets `value` share, token.address gets `reenterValue` shares - await vault.deposit(value, holder, { from: holder }); - await vault.deposit(reenterValue, token.address, { from: other }); + await this.vault.connect(this.holder).deposit(value, this.holder); + await this.vault.connect(this.other).deposit(reenterValue, this.token); // Schedules a reentrancy from the token contract - await token.scheduleReenter( + await this.token.scheduleReenter( reenterType.After, - vault.address, - vault.contract.methods.withdraw(reenterValue, holder, token.address).encodeABI(), + this.vault, + this.vault.interface.encodeFunctionData('withdraw', [reenterValue, this.holder.address, this.token.target]), ); // Initial share price - const sharesForWithdraw = await vault.previewWithdraw(value, { from: holder }); - const sharesForReenter = await vault.previewWithdraw(reenterValue, { from: holder }); + const sharesForWithdraw = await this.vault.previewWithdraw(value); + const sharesForReenter = await this.vault.previewWithdraw(reenterValue); // Do withdraw normally, triggering the _afterTokenTransfer hook - const receipt = await vault.withdraw(value, holder, holder, { from: holder }); - - // Main withdraw event - await expectEvent(receipt, 'Withdraw', { - sender: holder, - receiver: holder, - owner: holder, - assets: value, - shares: sharesForWithdraw, - }); - // Reentrant withdraw event → uses the same price - await expectEvent(receipt, 'Withdraw', { - sender: token.address, - receiver: holder, - owner: token.address, - assets: reenterValue, - shares: sharesForReenter, - }); + await expect(this.vault.connect(this.holder).withdraw(value, this.holder, this.holder)) + // Main withdraw event + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.holder.address, this.holder.address, value, sharesForWithdraw) + // Reentrant withdraw event → uses the same price + .to.emit(this.vault, 'Withdraw') + .withArgs(this.token.target, this.holder.address, this.token.target, reenterValue, sharesForReenter); // Assert price is kept - const sharesAfter = await vault.previewWithdraw(value, { from: holder }); - expect(sharesForWithdraw).to.be.bignumber.eq(sharesAfter); + expect(await this.vault.previewWithdraw(value)).to.equal(sharesForWithdraw); }); // Donate newly minted tokens to the vault during the reentracy causes the share price to increase. @@ -161,254 +137,210 @@ contract('ERC4626', function (accounts) { // Further deposits will get a different price (getting fewer shares for the same value of assets) it('share price change during reentracy does not affect deposit', async function () { // Schedules a reentrancy from the token contract that mess up the share price - await token.scheduleReenter( + await this.token.scheduleReenter( reenterType.Before, - token.address, - token.contract.methods.$_mint(vault.address, reenterValue).encodeABI(), + this.token, + this.token.interface.encodeFunctionData('$_mint', [this.vault.target, reenterValue]), ); // Price before - const sharesBefore = await vault.previewDeposit(value); + const sharesBefore = await this.vault.previewDeposit(value); // Deposit, reentering before the internal `_update` - const receipt = await vault.deposit(value, holder, { from: holder }); - - // Price is as previewed - await expectEvent(receipt, 'Deposit', { - sender: holder, - owner: holder, - assets: value, - shares: sharesBefore, - }); + await expect(this.vault.connect(this.holder).deposit(value, this.holder)) + // Price is as previewed + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.holder.address, value, sharesBefore); // Price was modified during reentrancy - const sharesAfter = await vault.previewDeposit(value); - expect(sharesAfter).to.be.bignumber.lt(sharesBefore); + expect(await this.vault.previewDeposit(value)).to.lt(sharesBefore); }); // Burn some tokens from the vault during the reentracy causes the share price to drop. // Still, the withdraw that trigger the reentracy is not affected and get the previewed price. // Further withdraw will get a different price (needing more shares for the same value of assets) it('share price change during reentracy does not affect withdraw', async function () { - await vault.deposit(value, other, { from: other }); - await vault.deposit(value, holder, { from: holder }); + await this.vault.connect(this.holder).deposit(value, this.holder); + await this.vault.connect(this.other).deposit(value, this.other); // Schedules a reentrancy from the token contract that mess up the share price - await token.scheduleReenter( + await this.token.scheduleReenter( reenterType.After, - token.address, - token.contract.methods.$_burn(vault.address, reenterValue).encodeABI(), + this.token, + this.token.interface.encodeFunctionData('$_burn', [this.vault.target, reenterValue]), ); // Price before - const sharesBefore = await vault.previewWithdraw(value); + const sharesBefore = await this.vault.previewWithdraw(value); // Withdraw, triggering the _afterTokenTransfer hook - const receipt = await vault.withdraw(value, holder, holder, { from: holder }); - - // Price is as previewed - await expectEvent(receipt, 'Withdraw', { - sender: holder, - receiver: holder, - owner: holder, - assets: value, - shares: sharesBefore, - }); + await expect(this.vault.connect(this.holder).withdraw(value, this.holder, this.holder)) + // Price is as previewed + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.holder.address, this.holder.address, value, sharesBefore); // Price was modified during reentrancy - const sharesAfter = await vault.previewWithdraw(value); - expect(sharesAfter).to.be.bignumber.gt(sharesBefore); + expect(await this.vault.previewWithdraw(value)).to.gt(sharesBefore); }); }); describe('limits', async function () { beforeEach(async function () { - this.token = await ERC20Decimals.new(name, symbol, decimals); - this.vault = await ERC4626LimitsMock.new(name + ' Vault', symbol + 'V', this.token.address); + const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); + const vault = await ethers.deployContract('$ERC4626LimitsMock', ['', '', token]); + + Object.assign(this, { token, vault }); }); it('reverts on deposit() above max deposit', async function () { - const maxDeposit = await this.vault.maxDeposit(holder); - await expectRevertCustomError(this.vault.deposit(maxDeposit.addn(1), recipient), 'ERC4626ExceededMaxDeposit', [ - recipient, - maxDeposit.addn(1), - maxDeposit, - ]); + const maxDeposit = await this.vault.maxDeposit(this.holder); + await expect(this.vault.connect(this.holder).deposit(maxDeposit + 1n, this.recipient)) + .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxDeposit') + .withArgs(this.recipient.address, maxDeposit + 1n, maxDeposit); }); it('reverts on mint() above max mint', async function () { - const maxMint = await this.vault.maxMint(holder); - await expectRevertCustomError(this.vault.mint(maxMint.addn(1), recipient), 'ERC4626ExceededMaxMint', [ - recipient, - maxMint.addn(1), - maxMint, - ]); + const maxMint = await this.vault.maxMint(this.holder); + + await expect(this.vault.connect(this.holder).mint(maxMint + 1n, this.recipient)) + .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxMint') + .withArgs(this.recipient.address, maxMint + 1n, maxMint); }); it('reverts on withdraw() above max withdraw', async function () { - const maxWithdraw = await this.vault.maxWithdraw(holder); - await expectRevertCustomError( - this.vault.withdraw(maxWithdraw.addn(1), recipient, holder), - 'ERC4626ExceededMaxWithdraw', - [holder, maxWithdraw.addn(1), maxWithdraw], - ); + const maxWithdraw = await this.vault.maxWithdraw(this.holder); + + await expect(this.vault.connect(this.holder).withdraw(maxWithdraw + 1n, this.recipient, this.holder)) + .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxWithdraw') + .withArgs(this.holder.address, maxWithdraw + 1n, maxWithdraw); }); it('reverts on redeem() above max redeem', async function () { - const maxRedeem = await this.vault.maxRedeem(holder); - await expectRevertCustomError( - this.vault.redeem(maxRedeem.addn(1), recipient, holder), - 'ERC4626ExceededMaxRedeem', - [holder, maxRedeem.addn(1), maxRedeem], - ); + const maxRedeem = await this.vault.maxRedeem(this.holder); + + await expect(this.vault.connect(this.holder).redeem(maxRedeem + 1n, this.recipient, this.holder)) + .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxRedeem') + .withArgs(this.holder.address, maxRedeem + 1n, maxRedeem); }); }); - for (const offset of [0, 6, 18].map(web3.utils.toBN)) { - const parseToken = token => web3.utils.toBN(10).pow(decimals).muln(token); - const parseShare = share => web3.utils.toBN(10).pow(decimals.add(offset)).muln(share); + for (const offset of [0n, 6n, 18n]) { + const parseToken = token => token * 10n ** decimals; + const parseShare = share => share * 10n ** (decimals + offset); - const virtualAssets = web3.utils.toBN(1); - const virtualShares = web3.utils.toBN(10).pow(offset); + const virtualAssets = 1n; + const virtualShares = 10n ** offset; describe(`offset: ${offset}`, function () { beforeEach(async function () { - this.token = await ERC20Decimals.new(name, symbol, decimals); - this.vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', this.token.address, offset); + const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); + const vault = await ethers.deployContract('$ERC4626OffsetMock', [name + ' Vault', symbol + 'V', token, offset]); - await this.token.$_mint(holder, constants.MAX_INT256); // 50% of maximum - await this.token.approve(this.vault.address, constants.MAX_UINT256, { from: holder }); - await this.vault.approve(spender, constants.MAX_UINT256, { from: holder }); + await token.$_mint(this.holder, ethers.MaxUint256 / 2n); // 50% of maximum + await token.$_approve(this.holder, vault, ethers.MaxUint256); + await vault.$_approve(this.holder, this.spender, ethers.MaxUint256); + + Object.assign(this, { token, vault }); }); it('metadata', async function () { - expect(await this.vault.name()).to.be.equal(name + ' Vault'); - expect(await this.vault.symbol()).to.be.equal(symbol + 'V'); - expect(await this.vault.decimals()).to.be.bignumber.equal(decimals.add(offset)); - expect(await this.vault.asset()).to.be.equal(this.token.address); + expect(await this.vault.name()).to.equal(name + ' Vault'); + expect(await this.vault.symbol()).to.equal(symbol + 'V'); + expect(await this.vault.decimals()).to.equal(decimals + offset); + expect(await this.vault.asset()).to.equal(this.token.target); }); describe('empty vault: no assets & no shares', function () { it('status', async function () { - expect(await this.vault.totalAssets()).to.be.bignumber.equal('0'); + expect(await this.vault.totalAssets()).to.equal(0n); }); it('deposit', async function () { - expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewDeposit(parseToken(1))).to.be.bignumber.equal(parseShare(1)); + expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewDeposit(parseToken(1n))).to.equal(parseShare(1n)); - const { tx } = await this.vault.deposit(parseToken(1), recipient, { from: holder }); + const tx = this.vault.connect(this.holder).deposit(parseToken(1n), this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: parseToken(1), - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: parseShare(1), - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: parseToken(1), - shares: parseShare(1), - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-parseToken(1n), parseToken(1n)], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, parseShare(1n)); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, parseToken(1n)) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, parseShare(1n)) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, parseToken(1n), parseShare(1n)); }); it('mint', async function () { - expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewMint(parseShare(1))).to.be.bignumber.equal(parseToken(1)); + expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewMint(parseShare(1n))).to.equal(parseToken(1n)); - const { tx } = await this.vault.mint(parseShare(1), recipient, { from: holder }); + const tx = this.vault.connect(this.holder).mint(parseShare(1n), this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: parseToken(1), - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: parseShare(1), - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: parseToken(1), - shares: parseShare(1), - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-parseToken(1n), parseToken(1n)], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, parseShare(1n)); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, parseToken(1n)) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, parseShare(1n)) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, parseToken(1n), parseShare(1n)); }); it('withdraw', async function () { - expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0'); - expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0'); + expect(await this.vault.maxWithdraw(this.holder)).to.equal(0n); + expect(await this.vault.previewWithdraw(0n)).to.equal(0n); - const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).withdraw(0n, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: '0', - shares: '0', - }); + await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, 0n) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, 0n) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, 0n, 0n); }); it('redeem', async function () { - expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0'); - expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0'); + expect(await this.vault.maxRedeem(this.holder)).to.equal(0n); + expect(await this.vault.previewRedeem(0n)).to.equal(0n); - const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).redeem(0n, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: '0', - shares: '0', - }); + await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, 0n) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, 0n) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, 0n, 0n); }); }); describe('inflation attack: offset price by direct deposit of assets', function () { beforeEach(async function () { // Donate 1 token to the vault to offset the price - await this.token.$_mint(this.vault.address, parseToken(1)); + await this.token.$_mint(this.vault, parseToken(1n)); }); it('status', async function () { - expect(await this.vault.totalSupply()).to.be.bignumber.equal('0'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1)); + expect(await this.vault.totalSupply()).to.equal(0n); + expect(await this.vault.totalAssets()).to.equal(parseToken(1n)); }); /** @@ -423,35 +355,30 @@ contract('ERC4626', function (accounts) { * was trying to deposit */ it('deposit', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const depositAssets = parseToken(1); - const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets); + const depositAssets = parseToken(1n); + const expectedShares = (depositAssets * effectiveShares) / effectiveAssets; - expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares); + expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewDeposit(depositAssets)).to.equal(expectedShares); - const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder }); + const tx = this.vault.connect(this.holder).deposit(depositAssets, this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: depositAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: expectedShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: depositAssets, - shares: expectedShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-depositAssets, depositAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, expectedShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, depositAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, expectedShares) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, depositAssets, expectedShares); }); /** @@ -466,102 +393,77 @@ contract('ERC4626', function (accounts) { * large deposits. */ it('mint', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const mintShares = parseShare(1); - const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares); + const mintShares = parseShare(1n); + const expectedAssets = (mintShares * effectiveAssets) / effectiveShares; - expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets); + expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewMint(mintShares)).to.equal(expectedAssets); - const { tx } = await this.vault.mint(mintShares, recipient, { from: holder }); + const tx = this.vault.connect(this.holder).mint(mintShares, this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: expectedAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: mintShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: expectedAssets, - shares: mintShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-expectedAssets, expectedAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, mintShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, expectedAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, mintShares) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, expectedAssets, mintShares); }); it('withdraw', async function () { - expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0'); - expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0'); + expect(await this.vault.maxWithdraw(this.holder)).to.equal(0n); + expect(await this.vault.previewWithdraw(0n)).to.equal(0n); - const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).withdraw(0n, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: '0', - shares: '0', - }); + await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, 0n) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, 0n) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, 0n, 0n); }); it('redeem', async function () { - expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0'); - expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0'); + expect(await this.vault.maxRedeem(this.holder)).to.equal(0n); + expect(await this.vault.previewRedeem(0n)).to.equal(0n); - const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).redeem(0n, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: '0', - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: '0', - shares: '0', - }); + await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, 0n) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, 0n) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, 0n, 0n); }); }); describe('full vault: assets & shares', function () { beforeEach(async function () { // Add 1 token of underlying asset and 100 shares to the vault - await this.token.$_mint(this.vault.address, parseToken(1)); - await this.vault.$_mint(holder, parseShare(100)); + await this.token.$_mint(this.vault, parseToken(1n)); + await this.vault.$_mint(this.holder, parseShare(100n)); }); it('status', async function () { - expect(await this.vault.totalSupply()).to.be.bignumber.equal(parseShare(100)); - expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1)); + expect(await this.vault.totalSupply()).to.equal(parseShare(100n)); + expect(await this.vault.totalAssets()).to.equal(parseToken(1n)); }); /** @@ -574,35 +476,30 @@ contract('ERC4626', function (accounts) { * Virtual shares & assets captures part of the value */ it('deposit', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const depositAssets = parseToken(1); - const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets); + const depositAssets = parseToken(1n); + const expectedShares = (depositAssets * effectiveShares) / effectiveAssets; - expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares); + expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewDeposit(depositAssets)).to.equal(expectedShares); - const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder }); + const tx = this.vault.connect(this.holder).deposit(depositAssets, this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: depositAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: expectedShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: depositAssets, - shares: expectedShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-depositAssets, depositAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, expectedShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, depositAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, expectedShares) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, depositAssets, expectedShares); }); /** @@ -615,249 +512,216 @@ contract('ERC4626', function (accounts) { * Virtual shares & assets captures part of the value */ it('mint', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const mintShares = parseShare(1); - const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares).addn(1); // add for the rounding + const mintShares = parseShare(1n); + const expectedAssets = (mintShares * effectiveAssets) / effectiveShares + 1n; // add for the rounding - expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); - expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets); + expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); + expect(await this.vault.previewMint(mintShares)).to.equal(expectedAssets); - const { tx } = await this.vault.mint(mintShares, recipient, { from: holder }); + const tx = this.vault.connect(this.holder).mint(mintShares, this.recipient); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: expectedAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: mintShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: expectedAssets, - shares: mintShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault], + [-expectedAssets, expectedAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.recipient, mintShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, expectedAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, mintShares) + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, expectedAssets, mintShares); }); it('withdraw', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const withdrawAssets = parseToken(1); - const expectedShares = withdrawAssets.mul(effectiveShares).div(effectiveAssets).addn(1); // add for the rounding + const withdrawAssets = parseToken(1n); + const expectedShares = (withdrawAssets * effectiveShares) / effectiveAssets + 1n; // add for the rounding - expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal(withdrawAssets); - expect(await this.vault.previewWithdraw(withdrawAssets)).to.be.bignumber.equal(expectedShares); + expect(await this.vault.maxWithdraw(this.holder)).to.equal(withdrawAssets); + expect(await this.vault.previewWithdraw(withdrawAssets)).to.equal(expectedShares); - const { tx } = await this.vault.withdraw(withdrawAssets, recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).withdraw(withdrawAssets, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: withdrawAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: expectedShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: withdrawAssets, - shares: expectedShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.vault, this.recipient], + [-withdrawAssets, withdrawAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, -expectedShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, withdrawAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, expectedShares) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, withdrawAssets, expectedShares); }); it('withdraw with approval', async function () { - const assets = await this.vault.previewWithdraw(parseToken(1)); - await expectRevertCustomError( - this.vault.withdraw(parseToken(1), recipient, holder, { from: other }), - 'ERC20InsufficientAllowance', - [other, 0, assets], - ); + const assets = await this.vault.previewWithdraw(parseToken(1n)); - await this.vault.withdraw(parseToken(1), recipient, holder, { from: spender }); + await expect(this.vault.connect(this.other).withdraw(parseToken(1n), this.recipient, this.holder)) + .to.be.revertedWithCustomError(this.vault, 'ERC20InsufficientAllowance') + .withArgs(this.other.address, 0n, assets); + + await expect(this.vault.connect(this.spender).withdraw(parseToken(1n), this.recipient, this.holder)).to.not.be + .reverted; }); it('redeem', async function () { - const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); - const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; + const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - const redeemShares = parseShare(100); - const expectedAssets = redeemShares.mul(effectiveAssets).div(effectiveShares); + const redeemShares = parseShare(100n); + const expectedAssets = (redeemShares * effectiveAssets) / effectiveShares; - expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal(redeemShares); - expect(await this.vault.previewRedeem(redeemShares)).to.be.bignumber.equal(expectedAssets); + expect(await this.vault.maxRedeem(this.holder)).to.equal(redeemShares); + expect(await this.vault.previewRedeem(redeemShares)).to.equal(expectedAssets); - const { tx } = await this.vault.redeem(redeemShares, recipient, holder, { from: holder }); + const tx = this.vault.connect(this.holder).redeem(redeemShares, this.recipient, this.holder); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: expectedAssets, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: redeemShares, - }); - - await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: expectedAssets, - shares: redeemShares, - }); + await expect(tx).to.changeTokenBalances( + this.token, + [this.vault, this.recipient], + [-expectedAssets, expectedAssets], + ); + await expect(tx).to.changeTokenBalance(this.vault, this.holder, -redeemShares); + await expect(tx) + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, expectedAssets) + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, redeemShares) + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, expectedAssets, redeemShares); }); it('redeem with approval', async function () { - await expectRevertCustomError( - this.vault.redeem(parseShare(100), recipient, holder, { from: other }), - 'ERC20InsufficientAllowance', - [other, 0, parseShare(100)], - ); + await expect(this.vault.connect(this.other).redeem(parseShare(100n), this.recipient, this.holder)) + .to.be.revertedWithCustomError(this.vault, 'ERC20InsufficientAllowance') + .withArgs(this.other.address, 0n, parseShare(100n)); - await this.vault.redeem(parseShare(100), recipient, holder, { from: spender }); + await expect(this.vault.connect(this.spender).redeem(parseShare(100n), this.recipient, this.holder)).to.not.be + .reverted; }); }); }); } describe('ERC4626Fees', function () { - const feeBasisPoints = web3.utils.toBN(5e3); - const valueWithoutFees = web3.utils.toBN(10000); - const fees = valueWithoutFees.mul(feeBasisPoints).divn(1e4); - const valueWithFees = valueWithoutFees.add(fees); + const feeBasisPoints = 500n; // 5% + const valueWithoutFees = 10_000n; + const fees = (valueWithoutFees * feeBasisPoints) / 10_000n; + const valueWithFees = valueWithoutFees + fees; describe('input fees', function () { beforeEach(async function () { - this.token = await ERC20Decimals.new(name, symbol, 18); - this.vault = await ERC4626FeesMock.new( - name + ' Vault', - symbol + 'V', - this.token.address, + const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); + const vault = await ethers.deployContract('$ERC4626FeesMock', [ + '', + '', + token, feeBasisPoints, - other, - 0, - constants.ZERO_ADDRESS, - ); + this.other, + 0n, + ethers.ZeroAddress, + ]); - await this.token.$_mint(holder, constants.MAX_INT256); - await this.token.approve(this.vault.address, constants.MAX_INT256, { from: holder }); + await token.$_mint(this.holder, ethers.MaxUint256 / 2n); + await token.$_approve(this.holder, vault, ethers.MaxUint256 / 2n); + + Object.assign(this, { token, vault }); }); it('deposit', async function () { - expect(await this.vault.previewDeposit(valueWithFees)).to.be.bignumber.equal(valueWithoutFees); - ({ tx: this.tx } = await this.vault.deposit(valueWithFees, recipient, { from: holder })); + expect(await this.vault.previewDeposit(valueWithFees)).to.equal(valueWithoutFees); + this.tx = this.vault.connect(this.holder).deposit(valueWithFees, this.recipient); }); it('mint', async function () { - expect(await this.vault.previewMint(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); - ({ tx: this.tx } = await this.vault.mint(valueWithoutFees, recipient, { from: holder })); + expect(await this.vault.previewMint(valueWithoutFees)).to.equal(valueWithFees); + this.tx = this.vault.connect(this.holder).mint(valueWithoutFees, this.recipient); }); afterEach(async function () { - // get total - await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { - from: holder, - to: this.vault.address, - value: valueWithFees, - }); - - // redirect fees - await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { - from: this.vault.address, - to: other, - value: fees, - }); - - // mint shares - await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: recipient, - value: valueWithoutFees, - }); - - // deposit event - await expectEvent.inTransaction(this.tx, this.vault, 'Deposit', { - sender: holder, - owner: recipient, - assets: valueWithFees, - shares: valueWithoutFees, - }); + await expect(this.tx).to.changeTokenBalances( + this.token, + [this.holder, this.vault, this.other], + [-valueWithFees, valueWithoutFees, fees], + ); + await expect(this.tx).to.changeTokenBalance(this.vault, this.recipient, valueWithoutFees); + await expect(this.tx) + // get total + .to.emit(this.token, 'Transfer') + .withArgs(this.holder.address, this.vault.target, valueWithFees) + // redirect fees + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.other.address, fees) + // mint shares + .to.emit(this.vault, 'Transfer') + .withArgs(ethers.ZeroAddress, this.recipient.address, valueWithoutFees) + // deposit event + .to.emit(this.vault, 'Deposit') + .withArgs(this.holder.address, this.recipient.address, valueWithFees, valueWithoutFees); }); }); describe('output fees', function () { beforeEach(async function () { - this.token = await ERC20Decimals.new(name, symbol, 18); - this.vault = await ERC4626FeesMock.new( - name + ' Vault', - symbol + 'V', - this.token.address, - 0, - constants.ZERO_ADDRESS, - 5e3, // 5% - other, - ); + const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); + const vault = await ethers.deployContract('$ERC4626FeesMock', [ + '', + '', + token, + 0n, + ethers.ZeroAddress, + feeBasisPoints, + this.other, + ]); - await this.token.$_mint(this.vault.address, constants.MAX_INT256); - await this.vault.$_mint(holder, constants.MAX_INT256); + await token.$_mint(vault, ethers.MaxUint256 / 2n); + await vault.$_mint(this.holder, ethers.MaxUint256 / 2n); + + Object.assign(this, { token, vault }); }); it('redeem', async function () { - expect(await this.vault.previewRedeem(valueWithFees)).to.be.bignumber.equal(valueWithoutFees); - ({ tx: this.tx } = await this.vault.redeem(valueWithFees, recipient, holder, { from: holder })); + expect(await this.vault.previewRedeem(valueWithFees)).to.equal(valueWithoutFees); + this.tx = this.vault.connect(this.holder).redeem(valueWithFees, this.recipient, this.holder); }); it('withdraw', async function () { - expect(await this.vault.previewWithdraw(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); - ({ tx: this.tx } = await this.vault.withdraw(valueWithoutFees, recipient, holder, { from: holder })); + expect(await this.vault.previewWithdraw(valueWithoutFees)).to.equal(valueWithFees); + this.tx = this.vault.connect(this.holder).withdraw(valueWithoutFees, this.recipient, this.holder); }); afterEach(async function () { - // withdraw principal - await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { - from: this.vault.address, - to: recipient, - value: valueWithoutFees, - }); - - // redirect fees - await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { - from: this.vault.address, - to: other, - value: fees, - }); - - // mint shares - await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { - from: holder, - to: constants.ZERO_ADDRESS, - value: valueWithFees, - }); - - // withdraw event - await expectEvent.inTransaction(this.tx, this.vault, 'Withdraw', { - sender: holder, - receiver: recipient, - owner: holder, - assets: valueWithoutFees, - shares: valueWithFees, - }); + await expect(this.tx).to.changeTokenBalances( + this.token, + [this.vault, this.recipient, this.other], + [-valueWithFees, valueWithoutFees, fees], + ); + await expect(this.tx).to.changeTokenBalance(this.vault, this.holder, -valueWithFees); + await expect(this.tx) + // withdraw principal + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.recipient.address, valueWithoutFees) + // redirect fees + .to.emit(this.token, 'Transfer') + .withArgs(this.vault.target, this.other.address, fees) + // mint shares + .to.emit(this.vault, 'Transfer') + .withArgs(this.holder.address, ethers.ZeroAddress, valueWithFees) + // withdraw event + .to.emit(this.vault, 'Withdraw') + .withArgs(this.holder.address, this.recipient.address, this.holder.address, valueWithoutFees, valueWithFees); }); }); }); @@ -866,244 +730,161 @@ contract('ERC4626', function (accounts) { /// https://github.com/transmissions11/solmate/blob/main/src/test/ERC4626.t.sol it('multiple mint, deposit, redeem & withdrawal', async function () { // test designed with both asset using similar decimals - this.token = await ERC20Decimals.new(name, symbol, 18); - this.vault = await ERC4626.new(name + ' Vault', symbol + 'V', this.token.address); + const [alice, bruce] = this.accounts; + const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); + const vault = await ethers.deployContract('$ERC4626', ['', '', token]); - await this.token.$_mint(user1, 4000); - await this.token.$_mint(user2, 7001); - await this.token.approve(this.vault.address, 4000, { from: user1 }); - await this.token.approve(this.vault.address, 7001, { from: user2 }); + await token.$_mint(alice, 4000n); + await token.$_mint(bruce, 7001n); + await token.connect(alice).approve(vault, 4000n); + await token.connect(bruce).approve(vault, 7001n); // 1. Alice mints 2000 shares (costs 2000 tokens) - { - const { tx } = await this.vault.mint(2000, user1, { from: user1 }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: user1, - to: this.vault.address, - value: '2000', - }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user1, - value: '2000', - }); + await expect(vault.connect(alice).mint(2000n, alice)) + .to.emit(token, 'Transfer') + .withArgs(alice.address, vault.target, 2000n) + .to.emit(vault, 'Transfer') + .withArgs(ethers.ZeroAddress, alice.address, 2000n); - expect(await this.vault.previewDeposit(2000)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '2000', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('2000'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('2000'); - } + expect(await vault.previewDeposit(2000n)).to.equal(2000n); + expect(await vault.balanceOf(alice)).to.equal(2000n); + expect(await vault.balanceOf(bruce)).to.equal(0n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2000n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(0n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(2000n); + expect(await vault.totalSupply()).to.equal(2000n); + expect(await vault.totalAssets()).to.equal(2000n); - // 2. Bob deposits 4000 tokens (mints 4000 shares) - { - const { tx } = await this.vault.mint(4000, user2, { from: user2 }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: user2, - to: this.vault.address, - value: '4000', - }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user2, - value: '4000', - }); + // 2. Bruce deposits 4000 tokens (mints 4000 shares) + await expect(vault.connect(bruce).mint(4000n, bruce)) + .to.emit(token, 'Transfer') + .withArgs(bruce.address, vault.target, 4000n) + .to.emit(vault, 'Transfer') + .withArgs(ethers.ZeroAddress, bruce.address, 4000n); - expect(await this.vault.previewDeposit(4000)).to.be.bignumber.equal('4000'); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('4000'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '6000', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('6000'); - } + expect(await vault.previewDeposit(4000n)).to.equal(4000n); + expect(await vault.balanceOf(alice)).to.equal(2000n); + expect(await vault.balanceOf(bruce)).to.equal(4000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2000n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(4000n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6000n); + expect(await vault.totalSupply()).to.equal(6000n); + expect(await vault.totalAssets()).to.equal(6000n); // 3. Vault mutates by +3000 tokens (simulated yield returned from strategy) - await this.token.$_mint(this.vault.address, 3000); + await token.$_mint(vault, 3000n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2999'); // used to be 3000, but virtual assets/shares captures part of the yield - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('5999'); // used to be 6000, but virtual assets/shares captures part of the yield - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '6000', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('9000'); + expect(await vault.balanceOf(alice)).to.equal(2000n); + expect(await vault.balanceOf(bruce)).to.equal(4000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2999n); // used to be 3000, but virtual assets/shares captures part of the yield + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(5999n); // used to be 6000, but virtual assets/shares captures part of the yield + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6000n); + expect(await vault.totalSupply()).to.equal(6000n); + expect(await vault.totalAssets()).to.equal(9000n); // 4. Alice deposits 2000 tokens (mints 1333 shares) - { - const { tx } = await this.vault.deposit(2000, user1, { from: user1 }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: user1, - to: this.vault.address, - value: '2000', - }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user1, - value: '1333', - }); + await expect(vault.connect(alice).deposit(2000n, alice)) + .to.emit(token, 'Transfer') + .withArgs(alice.address, vault.target, 2000n) + .to.emit(vault, 'Transfer') + .withArgs(ethers.ZeroAddress, alice.address, 1333n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('6000'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '7333', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('7333'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('11000'); - } + expect(await vault.balanceOf(alice)).to.equal(3333n); + expect(await vault.balanceOf(bruce)).to.equal(4000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(4999n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(6000n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(7333n); + expect(await vault.totalSupply()).to.equal(7333n); + expect(await vault.totalAssets()).to.equal(11000n); - // 5. Bob mints 2000 shares (costs 3001 assets) - // NOTE: Bob's assets spent got rounded towards infinity + // 5. Bruce mints 2000 shares (costs 3001 assets) + // NOTE: Bruce's assets spent got rounded towards infinity // NOTE: Alices's vault assets got rounded towards infinity - { - const { tx } = await this.vault.mint(2000, user2, { from: user2 }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: user2, - to: this.vault.address, - value: '3000', // used to be 3001 - }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user2, - value: '2000', - }); + await expect(vault.connect(bruce).mint(2000n, bruce)) + .to.emit(token, 'Transfer') + .withArgs(bruce.address, vault.target, 3000n) + .to.emit(vault, 'Transfer') + .withArgs(ethers.ZeroAddress, bruce.address, 2000n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999'); // used to be 5000 - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('9000'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '9333', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('14000'); // used to be 14001 - } + expect(await vault.balanceOf(alice)).to.equal(3333n); + expect(await vault.balanceOf(bruce)).to.equal(6000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(4999n); // used to be 5000 + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(9000n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(9333n); + expect(await vault.totalSupply()).to.equal(9333n); + expect(await vault.totalAssets()).to.equal(14000n); // used to be 14001 // 6. Vault mutates by +3000 tokens // NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000. - await this.token.$_mint(this.vault.address, 3000); + await token.$_mint(vault, 3000n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('6070'); // used to be 6071 - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10928'); // used to be 10929 - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '9333', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('17000'); // used to be 17001 + expect(await vault.balanceOf(alice)).to.equal(3333n); + expect(await vault.balanceOf(bruce)).to.equal(6000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(6070n); // used to be 6071 + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(10928n); // used to be 10929 + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(9333n); + expect(await vault.totalSupply()).to.equal(9333n); + expect(await vault.totalAssets()).to.equal(17000n); // used to be 17001 // 7. Alice redeem 1333 shares (2428 assets) - { - const { tx } = await this.vault.redeem(1333, user1, user1, { from: user1 }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: user1, - to: constants.ZERO_ADDRESS, - value: '1333', - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: user1, - value: '2427', // used to be 2428 - }); + await expect(vault.connect(alice).redeem(1333n, alice, alice)) + .to.emit(vault, 'Transfer') + .withArgs(alice.address, ethers.ZeroAddress, 1333n) + .to.emit(token, 'Transfer') + .withArgs(vault.target, alice.address, 2427n); // used to be 2428 - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10929'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '8000', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('8000'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('14573'); - } + expect(await vault.balanceOf(alice)).to.equal(2000n); + expect(await vault.balanceOf(bruce)).to.equal(6000n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(3643n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(10929n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(8000n); + expect(await vault.totalSupply()).to.equal(8000n); + expect(await vault.totalAssets()).to.equal(14573n); - // 8. Bob withdraws 2929 assets (1608 shares) - { - const { tx } = await this.vault.withdraw(2929, user2, user2, { from: user2 }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: user2, - to: constants.ZERO_ADDRESS, - value: '1608', - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: user2, - value: '2929', - }); + // 8. Bruce withdraws 2929 assets (1608 shares) + await expect(vault.connect(bruce).withdraw(2929n, bruce, bruce)) + .to.emit(vault, 'Transfer') + .withArgs(bruce.address, ethers.ZeroAddress, 1608n) + .to.emit(token, 'Transfer') + .withArgs(vault.target, bruce.address, 2929n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '6392', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('6392'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('11644'); - } + expect(await vault.balanceOf(alice)).to.equal(2000n); + expect(await vault.balanceOf(bruce)).to.equal(4392n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(3643n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(8000n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6392n); + expect(await vault.totalSupply()).to.equal(6392n); + expect(await vault.totalAssets()).to.equal(11644n); // 9. Alice withdraws 3643 assets (2000 shares) - // NOTE: Bob's assets have been rounded back towards infinity - { - const { tx } = await this.vault.withdraw(3643, user1, user1, { from: user1 }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: user1, - to: constants.ZERO_ADDRESS, - value: '2000', - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: user1, - value: '3643', - }); + // NOTE: Bruce's assets have been rounded back towards infinity + await expect(vault.connect(alice).withdraw(3643n, alice, alice)) + .to.emit(vault, 'Transfer') + .withArgs(alice.address, ethers.ZeroAddress, 2000n) + .to.emit(token, 'Transfer') + .withArgs(vault.target, alice.address, 3643n); - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000'); // used to be 8001 - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '4392', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('4392'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('8001'); - } + expect(await vault.balanceOf(alice)).to.equal(0n); + expect(await vault.balanceOf(bruce)).to.equal(4392n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(0n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(8000n); // used to be 8001 + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(4392n); + expect(await vault.totalSupply()).to.equal(4392n); + expect(await vault.totalAssets()).to.equal(8001n); - // 10. Bob redeem 4392 shares (8001 tokens) - { - const { tx } = await this.vault.redeem(4392, user2, user2, { from: user2 }); - await expectEvent.inTransaction(tx, this.vault, 'Transfer', { - from: user2, - to: constants.ZERO_ADDRESS, - value: '4392', - }); - await expectEvent.inTransaction(tx, this.token, 'Transfer', { - from: this.vault.address, - to: user2, - value: '8000', // used to be 8001 - }); + // 10. Bruce redeem 4392 shares (8001 tokens) + await expect(vault.connect(bruce).redeem(4392n, bruce, bruce)) + .to.emit(vault, 'Transfer') + .withArgs(bruce.address, ethers.ZeroAddress, 4392n) + .to.emit(token, 'Transfer') + .withArgs(vault.target, bruce.address, 8000n); // used to be 8001 - expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0'); - expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0'); - expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0'); - expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( - '0', - ); - expect(await this.vault.totalSupply()).to.be.bignumber.equal('0'); - expect(await this.vault.totalAssets()).to.be.bignumber.equal('1'); // used to be 0 - } + expect(await vault.balanceOf(alice)).to.equal(0n); + expect(await vault.balanceOf(bruce)).to.equal(0n); + expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(0n); + expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(0n); + expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(0n); + expect(await vault.totalSupply()).to.equal(0n); + expect(await vault.totalAssets()).to.equal(1n); // used to be 0 }); }); diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js index 2b88f5f8e..03a5b7cca 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -3,6 +3,7 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { getDomain, domainSeparator, hashTypedData } = require('../../helpers/eip712'); +const { formatType } = require('../../helpers/eip712-types'); const { getChainId } = require('../../helpers/chainid'); const LENGTHS = { @@ -77,10 +78,10 @@ describe('EIP712', function () { it('digest', async function () { const types = { - Mail: [ - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' }, - ], + Mail: formatType({ + to: 'address', + contents: 'string', + }), }; const message = {