@gooddollar/goodprotocol
Version:
GoodDollar Protocol
414 lines (343 loc) • 19.3 kB
text/typescript
import hre, { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { InvitesV2, IGoodDollar, IIdentity, IdentityV2 } from "../../types";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
import IdentityABI from "@gooddollar/goodcontracts/build/contracts/Identity.json";
import { createDAO } from "../helpers";
const BN = ethers.BigNumber;
describe("InvitesV2", () => {
let invites: InvitesV2, founder: SignerWithAddress;
let inviter1, inviter2, invitee1, invitee2, invitee3, invitee4, invitee5, invitee6, invitee7, invitee8;
let avatar, gd: IGoodDollar, Controller, id: IdentityV2, setDAOAddress, setSchemes;
const initialState = async () => {};
before(async () => {
[founder, inviter1, inviter2, invitee1, invitee2, invitee3, invitee4, invitee5, invitee6, invitee7, invitee8] =
await ethers.getSigners();
const InvitesV2 = await ethers.getContractFactory("InvitesV2");
let {
controller,
avatar: av,
gd: gooddollar,
identity,
nameService,
setDAOAddress: sda,
setSchemes: sc
} = await loadFixture(createDAO);
Controller = controller;
avatar = av;
setDAOAddress = sda;
setSchemes = sc;
invites = (await upgrades.deployProxy(InvitesV2, [nameService.address, 500, founder.address], {
kind: "uups"
})) as InvitesV2;
gd = (await ethers.getContractAt("IGoodDollar", gooddollar, founder)) as IGoodDollar;
id = (await ethers.getContractAt("IdentityV2", identity, founder)) as IdentityV2;
await gd["mint(address,uint256)"](invites.address, BN.from(5000));
await loadFixture(initialState);
// await gd.transfer(invites.address, BN.from(5000));
});
it("v1 should be upgradeable via old proxy method", async () => {
const InvitesV1 = await ethers.getContractFactory("InvitesV1");
const invites = await upgrades.deployProxy(InvitesV1, [avatar, id.address, gd.address, 500], {
kind: "transparent"
});
const res = await upgrades.upgradeProxy(invites.address, await ethers.getContractFactory("InvitesFuseV2"), {
kind: "transparent",
unsafeAllowRenames: true
});
expect(res).not.empty;
});
it("should have balance", async () => {
const balance = await gd.balanceOf(invites.address);
expect(balance).to.equal(5000);
});
it("should have version", async () => {
expect(await invites.active()).to.be.true;
const version = await invites.version();
expect(version).to.be.equal("2.3");
});
it("should let anyone join", async () => {
await invites.connect(inviter1).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero);
let inviter = await invites.users(inviter1.address);
expect(inviter.inviteCode).to.equal(ethers.utils.hexZeroPad("0xfa", 32));
});
it("should allow to join only once", async () => {
await expect(
invites.connect(inviter1).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.utils.hexZeroPad("0x01", 32))
).to.revertedWith(/user already joined/);
});
it("should not allow code reuse", async () => {
// const invites = await Invites.deployed();
await expect(
invites.connect(inviter2).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero)
).to.revertedWith(/invite code already in use/);
});
it("should mark inviter", async () => {
await invites.connect(invitee1).join(ethers.utils.hexZeroPad("0xaa", 32), ethers.utils.hexZeroPad("0xfa", 32));
let invitee = await invites.users(invitee1.address);
let inviterInvitees = await invites.getInvitees(inviter1.address);
expect(invitee.invitedBy).to.be.equal(inviter1.address);
expect(inviterInvitees).to.include(invitee1.address);
});
it("should not pay bounty for non whitelisted invitee", async () => {
await expect(invites.connect(inviter1).bountyFor(invitee1.address)).to.revertedWith(
/user not elligble for bounty yet/
);
});
it("should not allow to pay bounty for non whitelisted inviter", async () => {
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "");
expect(await id.isWhitelisted(invitee1.address)).to.be.true;
expect(await id.isWhitelisted(inviter1.address)).to.be.false;
expect(await id.getWhitelistedOnChainId(invitee1.address)).eq(4447);
expect(await invites.canCollectBountyFor(invitee1.address)).to.be.false;
await expect(invites.bountyFor(invitee1.address)).reverted;
});
it("should pay bounty for whitelisted invitee and inviter", async () => {
const bounty = (await invites.levels(0)).bounty.toNumber();
await id.addWhitelistedWithDID(inviter1.address, Math.random() + "").catch(e => e);
const startBalance = await gd.balanceOf(inviter1.address).then(_ => _.toNumber());
expect(await id.isWhitelisted(inviter1.address)).to.be.true;
let pending = await invites.getPendingInvitees(inviter1.address);
expect(pending.length, "pending").to.be.equal(1);
const inviteeBalance = await gd.balanceOf(invitee1.address).then(_ => _.toNumber());
await invites.connect(inviter1).bountyFor(invitee1.address);
let invitee = await invites.users(invitee1.address);
let inviter = await invites.users(inviter1.address);
const endBalance = await gd.balanceOf(inviter1.address).then(_ => _.toNumber());
pending = await invites.getPendingInvitees(inviter1.address);
const txFee = await gd["getFees(uint256)"](bounty).then(_ => _["0"].toNumber()); //gd might have a tx fee
const txFee2 = await gd["getFees(uint256)"](bounty / 2).then(_ => _["0"].toNumber()); //gd might have a tx fee
expect(pending.length, "pending").to.be.equal(0);
expect(invitee.bountyPaid).to.be.true;
expect(inviter.totalApprovedInvites.toNumber()).to.be.equal(1);
expect(inviter.totalEarned.toNumber()).to.be.equal(bounty);
expect(endBalance - startBalance + txFee, "inviter rewards not matching bounty").to.be.equal(bounty);
expect(
(await gd.balanceOf(invitee1.address).then(_ => _.toNumber())) - inviteeBalance,
"invitee rewrad should be bounty/2"
).to.be.equal(bounty / 2 - txFee2); //test that invitee got half bonus
});
it("should update global stats", async () => {
const bounty = (await invites.levels(0)).bounty.toNumber();
const stats = await invites.stats();
expect(stats.totalApprovedInvites.toNumber()).to.be.equal(1, "approved invites");
expect(stats.totalInvited.toNumber()).to.be.equal(1, "total invited");
expect(stats.totalBountiesPaid.toNumber()).to.be.equal(bounty);
});
it("should not pay bounty twice", async () => {
await expect(invites.connect(inviter2).bountyFor(invitee1.address)).to.revertedWith(
/user not elligble for bounty yet/
);
});
it("should not fail in collectBounties for invalid invitees", async () => {
await invites.connect(invitee7).join(ethers.utils.hexZeroPad("0x01", 32), ethers.utils.hexZeroPad("0xfa", 32));
await invites.connect(invitee8).join(ethers.utils.hexZeroPad("0x02", 32), ethers.utils.hexZeroPad("0xfa", 32));
let pending = await invites.getPendingInvitees(inviter1.address);
expect(pending.length, "pending").to.be.equal(2);
await expect(invites.connect(inviter1).collectBounties()).to.not.reverted;
let user1 = await invites.users(invitee7.address);
let user2 = await invites.users(invitee8.address);
pending = await invites.getPendingInvitees(inviter1.address);
expect(await invites.getPendingBounties(inviter1.address).then(_ => _.toNumber())).to.be.equal(0);
expect(user1.bountyPaid).to.be.false;
expect(user2.bountyPaid).to.be.false;
expect(pending.length, "pending").to.be.equal(2);
});
it("should collectBounties for inviter", async () => {
await id.addWhitelistedWithDID(invitee7.address, Math.random() + "");
await id.addWhitelistedWithDID(invitee8.address, Math.random() + "");
expect(await invites.getPendingBounties(inviter1.address).then(_ => _.toNumber())).to.be.equal(2);
const res = await invites.connect(inviter1).collectBounties();
let user1 = await invites.users(invitee7.address);
let user2 = await invites.users(invitee8.address);
let pending = await invites.getPendingInvitees(inviter1.address);
expect(await invites.getPendingBounties(inviter1.address).then(_ => _.toNumber())).to.be.equal(0);
expect(pending.length, "pending").to.be.equal(0);
expect(user1.bountyPaid, "user1").to.be.true;
expect(user2.bountyPaid, "user2").to.be.true;
});
it("should not set level not by owner", async () => {
await expect(invites.connect(inviter1).setLevel(0, 1, 5, 1)).to.revertedWith(
/Only owner or avatar can perform this action/
);
});
it("should set level by owner", async () => {
await invites.setLevel(0, 1, 5, 1);
let lvl = await invites.levels(0);
expect(lvl.toNext.toNumber()).to.be.equal(1);
expect(lvl.daysToComplete.toNumber()).to.be.equal(1);
await invites.setLevel(1, 0, 10, 2);
lvl = await invites.levels(1);
expect(lvl.toNext.toNumber()).to.be.equal(0);
expect(lvl.daysToComplete.toNumber()).to.be.equal(2);
expect(lvl.bounty.toNumber()).to.be.equal(10);
});
it("should update inviter level", async () => {
await invites
.connect(inviter1)
.join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero)
.then(_ => _.wait())
.catch(e => e);
await id.addWhitelistedWithDID(inviter1.address, Math.random() + "").catch(e => e);
await invites.setLevel(0, 1, 5, 1); //1 inviter to level up
await invites.setLevel(1, 0, 10, 2); // 10 bounty for second level
await invites.connect(invitee4).join(ethers.utils.hexZeroPad("0x03", 32), ethers.utils.hexZeroPad("0xfa", 32));
await invites.connect(invitee5).join(ethers.utils.hexZeroPad("0x04", 32), ethers.utils.hexZeroPad("0xfa", 32));
await id.addWhitelistedWithDID(invitee4.address, Math.random() + "").catch(e => e);
await id.addWhitelistedWithDID(invitee5.address, Math.random() + "").catch(e => e);
const res1 = await (await invites.bountyFor(invitee4.address)).wait();
const log1 = res1.events.find(_ => _.event === "InviterBounty");
expect(log1.event).to.be.equal("InviterBounty");
expect(log1.args.inviterLevel.toNumber()).to.be.equal(1);
expect(log1.args.earnedLevel).to.be.equal(true);
expect(log1.args.bountyPaid.toNumber()).to.be.equal(5);
let inviter = await invites.users(inviter1.address);
expect(inviter.level.toNumber()).to.be.equal(1);
const res2 = await (await invites.connect(inviter1).collectBounties()).wait();
const log2 = res2.events.find(_ => _.event === "InviterBounty");
expect(log2.event).to.be.equal("InviterBounty");
expect(log2.args.inviterLevel.toNumber()).to.be.equal(1);
expect(log2.args.earnedLevel).to.be.equal(false);
expect(log2.args.bountyPaid.toNumber()).to.be.equal(10);
});
it("should allow to set inviter later and pay bounty", async () => {
await invites.connect(invitee6).join(ethers.utils.hexZeroPad("0xfd", 32), ethers.constants.HashZero);
await invites.connect(invitee6).join(ethers.utils.hexZeroPad("0xfd", 32), ethers.utils.hexZeroPad("0xfa", 32));
const invitee = await invites.users(invitee6.address);
expect(invitee.invitedBy).to.equal(inviter1.address);
await id.addWhitelistedWithDID(invitee6.address, Math.random() + "").catch(e => e);
await expect(invites.bountyFor(invitee6.address)).to.emit(invites, "InviterBounty");
});
describe("MultiChain", () => {
it("should not revert if old identity contract without getWhitelistedOnChain", async () => {
await loadFixture(initialState);
const contractFactory = new ethers.ContractFactory(IdentityABI.abi, IdentityABI.bytecode, founder);
const oldId = await contractFactory.deploy();
await oldId.setAvatar(avatar);
await setSchemes([oldId.address], []);
await setDAOAddress("IDENTITY", oldId.address);
expect(await invites.getIdentity()).equal(oldId.address);
await invites.connect(inviter1).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero);
await invites.connect(invitee2).join(ethers.utils.hexZeroPad("0xaa", 32), ethers.utils.hexZeroPad("0xfa", 32));
await oldId.addWhitelistedWithDID(invitee2.address, Math.random() + "");
await oldId.addWhitelistedWithDID(inviter1.address, Math.random() + "");
await invites.connect(inviter1).bountyFor(invitee2.address);
});
it("should always be able to use my address as invite code", async () => {
await loadFixture(initialState);
await invites.connect(inviter1).join(ethers.utils.hexZeroPad(inviter1.address, 32), ethers.constants.HashZero);
await expect(
invites.connect(inviter2).join(ethers.utils.hexZeroPad(inviter2.address, 32), ethers.constants.HashZero)
).not.reverted;
await expect(
invites.connect(inviter1).join(ethers.utils.hexZeroPad(inviter1.address, 32), ethers.constants.HashZero)
).reverted;
});
it("should not allow to claim if whitelisted originally on another chain", async () => {
await loadFixture(initialState);
await invites.connect(inviter1).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero);
await invites.connect(invitee2).join(ethers.utils.hexZeroPad("0xaa", 32), ethers.utils.hexZeroPad("0xfa", 32));
await id.addWhitelistedWithDIDAndChain(invitee2.address, Math.random() + "", 122, 0);
expect(await id.getWhitelistedOnChainId(invitee2.address)).equal(122);
await expect(invites.connect(inviter1).bountyFor(invitee2.address)).to.revertedWith(
/user not elligble for bounty yet/
);
});
it("should allow to claim if whitelisted originally on same chain", async () => {
await loadFixture(initialState);
await invites.connect(inviter1).join(ethers.utils.hexZeroPad("0xfa", 32), ethers.constants.HashZero);
await invites.connect(invitee2).join(ethers.utils.hexZeroPad("0xaa", 32), ethers.utils.hexZeroPad("0xfa", 32));
await id.addWhitelistedWithDID(inviter1.address, Math.random() + "");
await id.addWhitelistedWithDIDAndChain(invitee2.address, Math.random() + "", 4447, 0);
expect(await id.getWhitelistedOnChainId(invitee2.address)).equal(4447);
await expect(invites.connect(inviter1).bountyFor(invitee2.address)).not.reverted;
});
it("should pay bounty on join for whitelisted invitee and inviter", async () => {
await loadFixture(initialState);
await id.addWhitelistedWithDID(inviter1.address, Math.random() + "").catch(e => e);
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
await invites.connect(inviter1).join(ethers.utils.hexZeroPad(inviter1.address, 32), ethers.constants.HashZero);
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.utils.hexZeroPad(inviter1.address, 32));
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
expect(bountyEvent).not.empty;
});
});
describe("Campaign Code", () => {
it("should not pay bounty on empty campaignCode", async () => {
await loadFixture(initialState);
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.constants.HashZero);
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
expect(bountyEvent).undefined;
});
it("should not pay bounty on wrong campaignCode", async () => {
await loadFixture(initialState);
await invites.setCampaignCode(ethers.utils.formatBytes32String("test"));
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.utils.formatBytes32String("test2"));
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
expect(bountyEvent).undefined;
});
it("should pay bounty with campaignCode", async () => {
await loadFixture(initialState);
await invites.setCampaignCode(ethers.utils.formatBytes32String("test"));
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.utils.formatBytes32String("test"));
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
const bounty = (await invites.levels(0)).bounty;
expect(await gd.balanceOf(invitee1.address)).eq(bounty.div(2));
expect(bountyEvent).not.empty;
});
it("should pay bounty with campaignCode after first join without code", async () => {
await loadFixture(initialState);
await invites.setCampaignCode(ethers.utils.formatBytes32String("test"));
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.constants.HashZero);
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
expect(bountyEvent).undefined;
const tx2 = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.utils.formatBytes32String("test"));
const { events: events2 } = await tx2.wait();
const bountyEvent2 = events2.find(_ => _.event === "InviterBounty");
expect(bountyEvent2).not.undefined;
});
it("should collect bounty with campaignCode after first join without being whitelisted", async () => {
await loadFixture(initialState);
await invites.setCampaignCode(ethers.utils.formatBytes32String("test"));
const tx = await invites
.connect(invitee1)
.join(ethers.utils.hexZeroPad(invitee1.address, 32), ethers.utils.formatBytes32String("test"));
const { events } = await tx.wait();
const bountyEvent = events.find(_ => _.event === "InviterBounty");
expect(bountyEvent).undefined;
await id.addWhitelistedWithDID(invitee1.address, Math.random() + "").catch(e => e);
expect(await invites.canCollectBountyFor(invitee1.address)).to.be.true;
const tx2 = await invites.connect(invitee1).bountyFor(invitee1.address);
const { events: events2 } = await tx2.wait();
const bountyEvent2 = events2.find(_ => _.event === "InviterBounty");
expect(bountyEvent2).not.undefined;
});
});
it("should end contract by owner", async () => {
expect(await gd.balanceOf(invites.address).then(_ => _.toNumber())).to.be.gt(0);
await invites.end();
expect(await gd.balanceOf(invites.address).then(_ => _.toNumber())).to.be.eq(0);
});
});