418 lines
14 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|