idle_moloch/lib/Baal/test/LootERC20.test.ts
2024-11-01 11:55:27 +01:00

418 lines
14 KiB
TypeScript

import { expect } from 'chai';
import { ethers, getChainId } from 'hardhat';
import signPermit from '../src/signPermit'
import { Loot, MockBaal } from '../src/types';
import { blockTime } from './utils/evm';
import { mockBaalSetup, Signer } from './utils/fixtures';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
const revertMessages = {
lootAlreadyInitialized: 'Initializable: contract is already initialized',
permitNotAuthorized: 'ERC20Permit: invalid signature',
permitExpired: 'ERC20Permit: expired deadline',
lootNotBaal: 'Ownable: caller is not the owner',
notTransferable: 'loot: !transferable',
transferToZero: 'ERC20: transfer to the zero address'
};
describe('Loot ERC20 contract', function () {
let lootSingleton: Loot;
let mockBaal: MockBaal;
let lootToken: Loot;
let chainId: number;
let users: {
[key: string]: Signer;
};
this.beforeAll(async function () {
chainId = Number(await getChainId());
});
beforeEach(async function () {
const { Loot, LootSingleton, MockBaal, signers } = await mockBaalSetup();
lootSingleton = LootSingleton;
lootToken = Loot;
mockBaal = MockBaal;
users = signers;
});
describe('constructor', async function () {
it('creates an unusable template', async () => {
expect(await lootSingleton.owner()).to.equal(ethers.constants.AddressZero);
});
it('require fail - initializer (setup) cant be called twice on loot', async () => {
await expect(lootToken.setUp('NAME', 'SYMBOL'))
.to.be.revertedWith(revertMessages.lootAlreadyInitialized);
});
it('require fail - initializer (setup) cant be called on singleton', async () => {
await expect(lootSingleton.setUp('NAME', 'SYMBOL'))
.to.be.revertedWith(revertMessages.lootAlreadyInitialized);
});
});
describe('er20 loot - authorized minting, burning', async function () {
it('happy case - allows baal to mint when loot not paused', async () => {
expect(await mockBaal.lootPaused()).to.equal(false);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(0);
await mockBaal.mintLoot(users.s2.address, 100);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(100);
});
it('happy case - allows baal to mint when loot paused', async () => {
await mockBaal.setLootPaused(true);
expect(await mockBaal.lootPaused()).to.equal(true);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(0);
await mockBaal.mintLoot(users.s2.address, 100);
expect(await lootToken.balanceOf(users.s2.address)).to.equal(100);
});
it('require fail - non baal tries to mint', async () => {
await expect(users.s1.loot?.mint(users.s1.address, 100))
.to.be.revertedWith(revertMessages.lootNotBaal);
});
it('happy case - allows baal to burn when loot not paused', async () => {
expect(await mockBaal.lootPaused()).to.equal(false);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(500);
await mockBaal.burnLoot(users.summoner.address, 100);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(400);
});
it('happy case - allows baal to burn when loot paused', async () => {
await mockBaal.setLootPaused(true);
expect(await mockBaal.lootPaused()).to.equal(true);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(500);
await mockBaal.burnLoot(users.summoner.address, 100);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(400);
});
it('require fail - non baal tries to burn', async () => {
await mockBaal.mintLoot(users.s2.address, 100);
await expect(users.s1.loot?.burn(users.s2.address, 50))
.to.be.revertedWith(revertMessages.lootNotBaal);
});
it('require fail - non baal tries to send to 0', async () => {
await mockBaal.mintLoot(users.s2.address, 100);
await expect(users.s1.loot?.transfer(ethers.constants.AddressZero, 50))
.to.be.revertedWith(revertMessages.transferToZero);
});
});
describe('er20 loot - restrict transfer', async function () {
it('happy case - allows loot to be transferred when enabled', async () => {
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(500);
expect(await lootToken.balanceOf(users.s1.address)).to.equal(0);
expect(await mockBaal.lootPaused()).to.equal(false);
await users.summoner.loot?.transfer(users.s1.address, 100);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(400);
expect(await lootToken.balanceOf(users.s1.address)).to.equal(100);
});
it('require fail - tries to transfer loot when paused', async () => {
await mockBaal.setLootPaused(true);
expect(await mockBaal.lootPaused()).to.equal(true);
await expect(lootToken.transfer(users.s1.address, 100))
.to.be.revertedWith(revertMessages.notTransferable);
});
it('happy case - allows loot to be transfered with approval when enabled', async () => {
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(500);
expect(await lootToken.balanceOf(users.s1.address)).to.equal(0);
expect(await mockBaal.lootPaused()).to.equal(false);
await users.summoner.loot?.approve(users.s2.address, 100);
await users.s2.loot?.transferFrom(users.summoner.address, users.s1.address, 100);
expect(await lootToken.balanceOf(users.summoner.address)).to.equal(400);
expect(await lootToken.balanceOf(users.s1.address)).to.equal(100);
});
it('require fail - tries to transfer with approval loot when paused', async () => {
await mockBaal.setLootPaused(true);
await users.summoner.loot?.approve(users.s2.address, 100);
await expect(
users.s2.loot?.transferFrom(
users.summoner.address,
users.s1.address,
100
)
).to.be.revertedWith(revertMessages.notTransferable);
});
})
describe('erc20 loot - increase allowance with permit', function () {
let signer: SignerWithAddress;
this.beforeEach(async function () {
signer = await ethers.getSigner(users.summoner.address);
});
it('happy case - increase allowance with valid permit', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId, // chainId
lootToken.address, // contractAddress
signer, // signer
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address, // owner
users.s1.address, // spender
500, // value
nonce, // nonce
deadline, // deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await lootToken.permit(users.summoner.address, users.s1.address, 500, deadline, v, r, s); // owner, spender, value, deadline, v, r, s
const s1Allowance = await lootToken.allowance(users.summoner.address, users.s1.address);
expect(s1Allowance).to.equal(500);
})
it('Require fail - invalid nonce', async () => {
const deadline = (await blockTime()) + 10000
const nonce = await lootToken.nonces(users.summoner.address)
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
500,
nonce.add(1),
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
})
it('Require fail - invalid chain Id', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
420,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
500,
nonce,
deadline
);
const {v,r,s} = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
})
it('Require fail - invalid name', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
'invalid',
users.summoner.address,
users.s1.address,
500,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
});
it('Require fail - invalid address', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address)
const permitSignature = await signPermit(
chainId,
ethers.constants.AddressZero,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
500,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
});
it('Require fail - invalid owner', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.s1.address,
users.s1.address,
500,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
})
it('Require fail - invalid spender', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s2.address,
500,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
});
it('Require fail - invalid amount', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
499,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
});
it('Require fail - invalid deadline', async () => {
const deadline = (await blockTime()) + 10000;
const nonce = await lootToken.nonces(users.summoner.address)
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
500,
nonce,
deadline - 1
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitNotAuthorized);
});
it('Require fail - expired deadline', async () => {
const deadline = (await blockTime()) - 1;
const nonce = await lootToken.nonces(users.summoner.address);
const permitSignature = await signPermit(
chainId,
lootToken.address,
signer,
await lootToken.name(), // name -- replacing await lootToken.name() with 'Loot' for new signing scope
users.summoner.address,
users.s1.address,
500,
nonce,
deadline
);
const { v, r, s } = await ethers.utils.splitSignature(permitSignature);
await expect(
lootToken.permit(
users.summoner.address,
users.s1.address,
500,
deadline,
v, r, s
)
).to.be.revertedWith(revertMessages.permitExpired);
});
});
});