Files
ragnar/test/PointsTokenFactory.ts
2024-04-17 11:26:12 -04:00

739 lines
25 KiB
TypeScript

import { expect } from "chai";
import { ethers } from "hardhat";
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { createPointsTokenSignature } from "./helpers/signatureHelpers";
import { PointsTokenRegistry, PointsTokenFactory, BountiesRegistry } from "../typechain-types";
const TOTAL_SUPPLY = 20_000_000 * 100; // * 100 because of two decimals
const DECIMALS = 2;
const FEE = ethers.parseEther("0.002");
describe("PointsTokenFactory", () => {
async function bountiesFixture() {
const [
owner,
custodian,
finance,
notary,
issuer,
maintainer,
contributor,
contributor2,
contributor3,
] = await ethers.getSigners();
const IdentityFactory = await ethers.getContractFactory("Identity");
const identity = await IdentityFactory.deploy(
custodian.address,
notary.address,
"http://localhost:3000",
);
const ClaimValidatorFactory = await ethers.getContractFactory("StaticClaimValidator");
const claimValidator = await ClaimValidatorFactory.deploy(true);
const BountiesConfigFactory = await ethers.getContractFactory("BountiesConfig");
const bountiesConfig = await BountiesConfigFactory.deploy(
custodian.address,
notary.address,
await identity.getAddress(),
await claimValidator.getAddress(),
[]
);
const BountiesFactory = await ethers.getContractFactory("Bounties");
const bounties = await BountiesFactory.deploy(
await bountiesConfig.getAddress(),
custodian.address,
finance.address,
);
const BountiesRegistryFactory = await ethers.getContractFactory("BountiesRegistry");
const bountiesRegistry = await BountiesRegistryFactory.deploy(custodian.address);
bountiesRegistry.connect(custodian).addBountiesContract(await bounties.getAddress());
return {
owner,
custodian,
bounties,
bountiesConfig,
bountiesRegistry,
identity,
finance,
notary,
issuer,
maintainer,
contributor,
contributor2,
contributor3,
};
}
async function createBountiesRegistry(custodian: HardhatEthersSigner) {
const bountiesRegistryFactory = await ethers.getContractFactory("BountiesRegistry");
return await bountiesRegistryFactory.deploy(custodian);
}
async function createPointsTokenRegistry(custodian: HardhatEthersSigner) {
const pointsTokenRegistryFactory = await ethers.getContractFactory(
"PointsTokenRegistry",
);
const pointsTokenRegistry = await pointsTokenRegistryFactory.deploy(custodian);
return pointsTokenRegistry;
}
async function createPointsTokenFactory(
custodian: HardhatEthersSigner,
finance: HardhatEthersSigner,
notary: HardhatEthersSigner,
bountiesRegistry: BountiesRegistry,
tokenRegistry: PointsTokenRegistry,
) {
const pointsFactoryFactory = await ethers.getContractFactory(
"PointsTokenFactory",
);
const pointsFactory = await pointsFactoryFactory.deploy(
custodian,
finance,
notary,
await bountiesRegistry.getAddress(),
await tokenRegistry.getAddress(),
DECIMALS,
TOTAL_SUPPLY,
FEE,
);
return pointsFactory;
}
async function pointsTokenFactoryFixture() {
const fixtures = await bountiesFixture();
const { bountiesRegistry, custodian, finance, notary } = fixtures;
const tokenRegistry = await createPointsTokenRegistry(custodian);
const pointsFactory = await createPointsTokenFactory(
custodian,
finance,
notary,
bountiesRegistry,
tokenRegistry,
);
return { ...fixtures, tokenRegistry, pointsFactory };
}
async function pFixture() {
const fixtures = await pointsTokenFactoryFixture();
const { custodian, bountiesConfig, pointsFactory, tokenRegistry } = fixtures;
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesConfig);
await bountiesConfig
.connect(custodian)
.grantRole(await bountiesConfig.TRUSTED_CONTRACT_ROLE(), pointsFactory);
tokenRegistry
.connect(custodian)
.grantRole(
await tokenRegistry.TRUSTED_CONTRACT_ROLE(),
pointsFactory.getAddress(),
);
return fixtures;
}
const BASE_PARAMS = ["Test Points", "cpTST", "1", "GitGig"] as const;
async function pointsTokenParamsFixture(
pointsFactory: PointsTokenFactory,
issuer: HardhatEthersSigner,
notary: HardhatEthersSigner,
) {
const sigParams = [...BASE_PARAMS, issuer.address];
const signature = await createPointsTokenSignature(
pointsFactory,
sigParams,
notary,
);
return [...BASE_PARAMS, signature];
}
async function applyCreatePointsToken(
pointsFactory: PointsTokenFactory,
issuer: HardhatEthersSigner,
params: any[],
fee = FEE,
) {
const { createPointsToken: realCreate } = pointsFactory.connect(issuer);
return realCreate.apply(pointsFactory, [...params, { value: fee }] as any);
}
async function createPointsToken(
pointsFactory: PointsTokenFactory,
issuer: HardhatEthersSigner,
notary: HardhatEthersSigner,
fee = FEE,
) {
const params = await pointsTokenParamsFixture(
pointsFactory,
issuer,
notary,
);
return applyCreatePointsToken(pointsFactory, issuer, params, fee);
}
describe("Deployment", () => {
it("should be able to deploy points factory contract", async () => {
const { pointsFactory } = await pFixture();
expect(pointsFactory.getAddress()).to.be.a.string;
});
});
describe("Create Points Token", () => {
it("should be able to create a points token", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
const tx = await createPointsToken(pointsFactory, issuer, notary);
expect(tx.hash).to.be.a.string;
});
it("should revert when paused", async () => {
const { pointsFactory, custodian, issuer, notary } = await pFixture();
await pointsFactory.connect(custodian).pause();
await expect(createPointsToken(pointsFactory, issuer, notary))
.to.revertedWithCustomError(pointsFactory, "EnforcedPause");
});
it("should revert if value is less than fee", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
const value = FEE - ethers.toBigInt(1);
await expect(createPointsToken(pointsFactory, issuer, notary, value))
.to.be.revertedWithCustomError(pointsFactory, "WrongFeeAmount")
.withArgs(value);
});
it("should revert if value is more than fee", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
const value = FEE + ethers.toBigInt(1);
await expect(createPointsToken(pointsFactory, issuer, notary, value))
.to.be.revertedWithCustomError(pointsFactory, "WrongFeeAmount")
.withArgs(value);
});
it("should register token as a supported token on bounties contract", async () => {
const { bountiesConfig, pointsFactory, issuer, notary, tokenRegistry } = await pFixture();
let tokenAddr = await tokenRegistry.getContract("1", "GitGig", "cpTST");
expect(tokenAddr).to.equal(ethers.ZeroAddress);
// when
await createPointsToken(pointsFactory, issuer, notary);
// then
tokenAddr = await tokenRegistry.getContract("1", "GitGig", "cpTST");
expect(tokenAddr).to.not.equal(ethers.ZeroAddress);
expect(await bountiesConfig.isSupportedToken(tokenAddr)).to.be.true;
});
it("should emit PointsTokenCreate event", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
await expect(createPointsToken(pointsFactory, issuer, notary))
.to.emit(pointsFactory, "PointsTokenCreate")
.withArgs(
anyValue,
"Test Points",
"cpTST",
DECIMALS,
TOTAL_SUPPLY,
issuer.address,
"1",
"GitGig",
);
});
it("should revert when symbol does not start with p", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
const params = ["Test Points", "TST", "1", "GitGig"];
const sigParams = [...params, issuer.address];
const signature = await createPointsTokenSignature(
pointsFactory,
sigParams,
notary,
);
await expect(
applyCreatePointsToken(pointsFactory, issuer, [...params, signature]),
)
.to.be.revertedWithCustomError(pointsFactory, "InvalidSymbol");
});
it("should revert with invalid signature", async () => {
const { pointsFactory, issuer } = await pFixture();
const params = ["Test Points", "cpTST", "1", "GitGig"];
const sigParams = [...params, issuer.address];
const wrongSignature = await createPointsTokenSignature(
pointsFactory,
sigParams,
issuer,
);
await expect(
applyCreatePointsToken(pointsFactory, issuer, [
...params,
wrongSignature,
]),
)
.to.be.revertedWithCustomError(pointsFactory, "InvalidSignature");
});
it("should register token in registry", async () => {
const { pointsFactory, issuer, notary, tokenRegistry } = await pFixture();
expect(
await tokenRegistry.getContract(
BASE_PARAMS[2],
BASE_PARAMS[3],
BASE_PARAMS[1],
),
)
.to.equal(ethers.ZeroAddress);
// when
await createPointsToken(pointsFactory, issuer, notary);
// then
expect(
await tokenRegistry.getContract(
BASE_PARAMS[2],
BASE_PARAMS[3],
BASE_PARAMS[1],
),
)
.to.not.equal(ethers.ZeroAddress);
});
it("should revert when owner already has symbol", async () => {
const { pointsFactory, issuer, notary, tokenRegistry } = await pFixture();
await createPointsToken(pointsFactory, issuer, notary);
await expect(createPointsToken(pointsFactory, issuer, notary))
.to.be.revertedWithCustomError(tokenRegistry, "SymbolAlreadyExists");
});
});
describe("Add Bounties Contract", () => {
it("should add contract to bountiesContracts", async () => {
const { bounties, custodian, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
// ensure not there
await expect(pointsFactory.bountiesConfigContracts(0)).to.be.reverted;
// when
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr);
expect(await pointsFactory.bountiesConfigContracts(0)).to.equal(bountiesAddr);
});
it("should not allow contract to be added twice", async () => {
const { bounties, custodian, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
// ensure not there
await expect(pointsFactory.bountiesConfigContracts(0)).to.be.reverted;
// when
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr);
// then
await expect(
pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr),
)
.to.be.revertedWithCustomError(pointsFactory, "InvalidArgument");
});
it("should revert when called by non-custodian", async () => {
const { bounties, issuer, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
// ensure not there
await expect(pointsFactory.bountiesConfigContracts(0)).to.be.reverted;
// when
await expect(
pointsFactory.connect(issuer).addBountiesConfigContract(bountiesAddr),
)
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
// still not there
await expect(pointsFactory.bountiesConfigContracts(0)).to.be.reverted;
});
it("should emit ConfigChange event", async () => {
const { bounties, custodian, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
// when/then
await expect(
pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr),
)
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Remove Bounties Config Contract", () => {
it("should remove contract from bountiesContracts", async () => {
const { bounties, custodian, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr);
expect(await pointsFactory.bountiesConfigContracts(0)).to.equal(bountiesAddr);
// when
await pointsFactory.connect(custodian).removeBountiesConfigContract(
bountiesAddr,
);
await expect(pointsFactory.bountiesConfigContracts(0)).to.be.reverted;
});
it("should revert when contract not in bountiesContracts", async () => {
const { bounties, bountiesRegistry, custodian, finance, notary } = await bountiesFixture();
const tokenRegistry = await createPointsTokenRegistry(custodian);
const pointsFactory = await createPointsTokenFactory(
custodian,
finance,
notary,
bountiesRegistry,
tokenRegistry,
);
const bountiesAddr = await bounties.getAddress();
// when
await expect(
pointsFactory.connect(custodian).removeBountiesConfigContract(bountiesAddr),
)
.to.be.revertedWithCustomError(pointsFactory, "InvalidArgument");
});
it("should revert when called by non-custodian", async () => {
const { bounties, bountiesRegistry, custodian, finance, issuer, notary } =
await bountiesFixture();
const tokenRegistry = await createPointsTokenRegistry(custodian);
const pointsFactory = await createPointsTokenFactory(
custodian,
finance,
notary,
bountiesRegistry,
tokenRegistry,
);
const bountiesAddr = await bounties.getAddress();
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr);
expect(await pointsFactory.bountiesConfigContracts(0)).to.equal(bountiesAddr);
// when
await expect(
pointsFactory.connect(issuer).removeBountiesConfigContract(bountiesAddr),
)
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
// then
expect(await pointsFactory.bountiesConfigContracts(0)).to.equal(bountiesAddr);
});
it("should emit ConfigChange event", async () => {
const { bounties, custodian, pointsFactory } = await pointsTokenFactoryFixture();
const bountiesAddr = await bounties.getAddress();
await pointsFactory.connect(custodian).addBountiesConfigContract(bountiesAddr);
expect(await pointsFactory.bountiesConfigContracts(0)).to.equal(bountiesAddr);
// when/then
await expect(
pointsFactory.connect(custodian).removeBountiesConfigContract(bountiesAddr),
)
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Decimals", () => {
it("should update decimals", async () => {
const { pointsFactory, custodian } = await pFixture();
await pointsFactory.connect(custodian).setDecimals(18);
expect(await pointsFactory.dec()).to.equal(18);
});
it("should revert with InvalidArgument when out of range", async () => {
const { pointsFactory, custodian } = await pFixture();
await expect(pointsFactory.connect(custodian).setDecimals(19))
.to.be.revertedWithCustomError(pointsFactory, "InvalidArgument");
expect(await pointsFactory.dec()).to.equal(DECIMALS); // default
});
it("should revert when called by non-custodian", async () => {
const { pointsFactory, issuer } = await pFixture();
await expect(pointsFactory.connect(issuer).setDecimals(18))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.dec()).to.equal(DECIMALS);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian } = await pFixture();
await expect(pointsFactory.connect(custodian).setDecimals(18))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Total Supply", () => {
it("should update total supply", async () => {
const { pointsFactory, custodian } = await pFixture();
await pointsFactory.connect(custodian).setTotalSupply(50_000);
expect(await pointsFactory.totalSupply()).to.equal(50_000);
});
it("should revert when called by non-custodian", async () => {
const { pointsFactory, issuer } = await pFixture();
await expect(pointsFactory.connect(issuer).setTotalSupply(100_000))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.totalSupply()).to.equal(TOTAL_SUPPLY);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian } = await pFixture();
await expect(pointsFactory.connect(custodian).setTotalSupply(50_000))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Fee", () => {
it("should update fee", async () => {
const { pointsFactory, custodian } = await pFixture();
await pointsFactory.connect(custodian).setFee(50_000);
expect(await pointsFactory.fee()).to.equal(50_000);
});
it("should revert when called by non-custodian", async () => {
const { pointsFactory, issuer } = await pFixture();
await expect(pointsFactory.connect(issuer).setFee(100_000))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.fee()).to.equal(FEE);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian } = await pFixture();
await expect(pointsFactory.connect(custodian).setFee(50_000))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Bounties Registry", () => {
it("should update registry", async () => {
const { custodian, pointsFactory } = await pointsTokenFactoryFixture();
const registry = await createBountiesRegistry(custodian);
const regAddr = await registry.getAddress();
await pointsFactory.connect(custodian).setBountiesRegistry(regAddr);
expect(await pointsFactory.bountiesRegistry()).to.equal(regAddr);
});
it("should revert when called by non-custodian", async () => {
const { custodian, pointsFactory, issuer } = await pFixture();
const oldRegAddr = await pointsFactory.bountiesRegistry();
const registry = await createBountiesRegistry(custodian);
const regAddr = await registry.getAddress();
await expect(pointsFactory.connect(issuer).setBountiesRegistry(regAddr))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.bountiesRegistry()).to.equal(oldRegAddr);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian } = await pFixture();
const registry = await createBountiesRegistry(custodian);
const regAddr = await registry.getAddress();
await expect(pointsFactory.connect(custodian).setBountiesRegistry(regAddr))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Token Registry", () => {
it("should update registry", async () => {
const { custodian, pointsFactory } = await pointsTokenFactoryFixture();
const registry = await createPointsTokenRegistry(custodian);
const regAddr = await registry.getAddress();
await pointsFactory.connect(custodian).setTokenRegistry(regAddr);
expect(await pointsFactory.tokenRegistry()).to.equal(regAddr);
});
it("should revert when called by non-custodian", async () => {
const { custodian, pointsFactory, issuer } = await pFixture();
const oldRegAddr = await pointsFactory.tokenRegistry();
const registry = await createPointsTokenRegistry(custodian);
await expect(pointsFactory.connect(issuer).setTokenRegistry(registry))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.tokenRegistry()).to.equal(oldRegAddr);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian } = await pFixture();
const registry = await createPointsTokenRegistry(custodian);
const regAddr = await registry.getAddress();
await expect(pointsFactory.connect(custodian).setTokenRegistry(regAddr))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Set Notary", () => {
it("should update notary", async () => {
const { pointsFactory, custodian, issuer } = await pFixture();
await pointsFactory.connect(custodian).setNotary(issuer.address);
expect(await pointsFactory.notary()).to.equal(issuer.address);
});
it("should revert when called by non-custodian", async () => {
const { pointsFactory, issuer, notary } = await pFixture();
await expect(pointsFactory.connect(issuer).setNotary(issuer.address))
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
expect(await pointsFactory.notary()).to.equal(notary.address);
});
it("should emit ConfigChange event", async () => {
const { pointsFactory, custodian, issuer } = await pFixture();
await expect(pointsFactory.connect(custodian).setNotary(issuer.address))
.to.emit(pointsFactory, "ConfigChange");
});
});
describe("Withdraw Fees", () => {
it("should withdraw all fees", async () => {
const { pointsFactory, issuer, notary, finance } = await pFixture();
await createPointsToken(pointsFactory, issuer, notary);
const finBal = await ethers.provider.getBalance(finance.address);
expect(await ethers.provider.getBalance(await pointsFactory.getAddress()))
.to.be.greaterThan(0);
// when
await pointsFactory.connect(finance).withdrawFees();
// then - ensure finance wallet increased value
expect(await ethers.provider.getBalance(await pointsFactory.getAddress()))
.to.equal(0);
expect(await ethers.provider.getBalance(finance.address)).to.be
.greaterThan(finBal);
});
it("should emit FeeWithdraw event", async () => {
const { pointsFactory, issuer, notary, finance } = await pFixture();
await createPointsToken(pointsFactory, issuer, notary);
// when/then
await expect(pointsFactory.connect(finance).withdrawFees())
.to.emit(pointsFactory, "FeeWithdraw")
.withArgs(finance.address, FEE);
});
it("should revert when called by non-finance", async () => {
const { custodian, pointsFactory, issuer, notary } = await pFixture();
await createPointsToken(pointsFactory, issuer, notary);
// when/then
await expect(pointsFactory.connect(custodian).withdrawFees())
.to.be.revertedWithCustomError(
pointsFactory,
"AccessControlUnauthorizedAccount",
);
});
});
describe("Pause", () => {
it('should pause', async () => {
const { pointsFactory, custodian } = await pFixture();
// when
await pointsFactory.connect(custodian).pause();
// then
expect(await pointsFactory.paused()).to.be.true;
});
it('should emit Paused event', async () => {
const { pointsFactory, custodian } = await pFixture();
// when
await expect(pointsFactory.connect(custodian).pause())
.to.emit(pointsFactory, "Paused")
.withArgs(custodian.address);
});
it('should revert when called by non-custodian', async () => {
const { pointsFactory, finance } = await pFixture();
// when
await expect(pointsFactory.connect(finance).pause())
.to.be.revertedWithCustomError(pointsFactory, "AccessControlUnauthorizedAccount");
});
});
describe("Unpause", () => {
it('should unpause', async () => {
const { pointsFactory, custodian } = await pFixture();
await pointsFactory.connect(custodian).pause();
expect(await pointsFactory.paused()).to.be.true;
await pointsFactory.connect(custodian).unpause();
// then
expect(await pointsFactory.paused()).to.be.false;
});
it('should emit Unpaused event', async () => {
const { pointsFactory, custodian } = await pFixture();
await pointsFactory.connect(custodian).pause();
expect(await pointsFactory.paused()).to.be.true;
await expect(pointsFactory.connect(custodian).unpause())
.to.emit(pointsFactory, "Unpaused")
.withArgs(custodian.address);
});
it('should revert when called by non-custodian', async () => {
const { pointsFactory, custodian, finance } = await pFixture();
await pointsFactory.connect(custodian).pause();
expect(await pointsFactory.paused()).to.be.true;
// when
await expect(pointsFactory.connect(finance).unpause())
.to.be.revertedWithCustomError(pointsFactory, "AccessControlUnauthorizedAccount");
});
});
});