UNPKG

@vechain/vebetterdao-contracts

Version:

Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.

771 lines 151 kB
import { ethers } from "hardhat"; import { expect } from "chai"; import { ZERO_ADDRESS, catchRevert, filterEventsByName, getOrDeployContractInstances, waitForRoundToEnd, } from "./helpers"; import { describe, it, before } from "mocha"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; import { deployProxy, upgradeProxy } from "../scripts/helpers"; import { endorseApp } from "./helpers/xnodes"; import { createLocalConfig } from "@repo/config/contracts/envs/local"; describe("X2EarnRewardsPool - @shard12", function () { // Environment params let creator1; let creator2; before(async function () { const { creators } = await getOrDeployContractInstances({ forceDeploy: true }); creator1 = creators[0]; creator2 = creators[1]; }); // deployment describe("Deployment", function () { it("Cannot deploy contract with zero address", async function () { const { owner } = await getOrDeployContractInstances({ forceDeploy: false, }); await expect(deployProxy("X2EarnRewardsPoolV1", [owner.address, owner.address, owner.address, owner.address, ZERO_ADDRESS])).to.be.reverted; await expect(deployProxy("X2EarnRewardsPoolV1", [owner.address, owner.address, owner.address, ZERO_ADDRESS, owner.address])).to.be.reverted; await expect(deployProxy("X2EarnRewardsPoolV1", [owner.address, owner.address, ZERO_ADDRESS, owner.address, owner.address])).to.be.reverted; await expect(deployProxy("X2EarnRewardsPoolV1", [owner.address, ZERO_ADDRESS, owner.address, owner.address, owner.address])).to.be.reverted; await expect(deployProxy("X2EarnRewardsPoolV1", [ZERO_ADDRESS, owner.address, owner.address, owner.address, owner.address])).to.be.reverted; }); it("Should deploy the contract", async function () { const { x2EarnRewardsPool } = await getOrDeployContractInstances({ forceDeploy: true }); expect(await x2EarnRewardsPool.getAddress()).to.not.equal(ZERO_ADDRESS); }); it("Should set B3TR correctly", async function () { const { x2EarnRewardsPool, b3tr } = await getOrDeployContractInstances({ forceDeploy: false }); expect(await x2EarnRewardsPool.b3tr()).to.equal(await b3tr.getAddress()); }); it("Version should be set correctly", async function () { const { x2EarnRewardsPool } = await getOrDeployContractInstances({ forceDeploy: false, }); expect(await x2EarnRewardsPool.version()).to.equal("8"); }); it("X2EarnApps should be set correctly", async function () { const { x2EarnRewardsPool, x2EarnApps } = await getOrDeployContractInstances({ forceDeploy: false }); expect(await x2EarnRewardsPool.x2EarnApps()).to.equal(await x2EarnApps.getAddress()); }); }); // upgradeability describe("Contract upgradeablity", () => { it("Cannot initialize twice", async function () { const { owner } = await getOrDeployContractInstances({ forceDeploy: true }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, owner.address, owner.address, ])); await catchRevert(x2EarnRewardsPoolV1.initialize(owner.address, owner.address, owner.address, owner.address, owner.address)); }); it("User with UPGRADER_ROLE should be able to upgrade the contract", async function () { const { x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("X2EarnRewardsPoolV1"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await x2EarnRewardsPool.getAddress()); const UPGRADER_ROLE = await x2EarnRewardsPool.UPGRADER_ROLE(); expect(await x2EarnRewardsPool.hasRole(UPGRADER_ROLE, owner.address)).to.eql(true); await expect(x2EarnRewardsPool.connect(owner).upgradeToAndCall(await implementation.getAddress(), "0x")).to.not.be .reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await x2EarnRewardsPool.getAddress()); expect(newImplAddress.toUpperCase()).to.not.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.eql((await implementation.getAddress()).toUpperCase()); }); it("Only user with UPGRADER_ROLE should be able to upgrade the contract", async function () { const { x2EarnRewardsPool, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); // Deploy the implementation contract const Contract = await ethers.getContractFactory("X2EarnRewardsPoolV1"); const implementation = await Contract.deploy(); await implementation.waitForDeployment(); const currentImplAddress = await getImplementationAddress(ethers.provider, await x2EarnRewardsPool.getAddress()); const UPGRADER_ROLE = await x2EarnRewardsPool.UPGRADER_ROLE(); expect(await x2EarnRewardsPool.hasRole(UPGRADER_ROLE, otherAccount.address)).to.eql(false); await expect(x2EarnRewardsPool.connect(otherAccount).upgradeToAndCall(await implementation.getAddress(), "0x")).to .be.reverted; const newImplAddress = await getImplementationAddress(ethers.provider, await x2EarnRewardsPool.getAddress()); expect(newImplAddress.toUpperCase()).to.eql(currentImplAddress.toUpperCase()); expect(newImplAddress.toUpperCase()).to.not.eql((await implementation.getAddress()).toUpperCase()); }); it("Should return correct version of the contract", async () => { const { x2EarnRewardsPool } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await x2EarnRewardsPool.version()).to.equal("8"); }); it("Storage should be preserved after upgrade", async () => { const config = createLocalConfig(); const { owner, b3tr, x2EarnApps, minterAccount, veBetterPassport } = await getOrDeployContractInstances({ forceDeploy: true, config, }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await x2EarnApps.getAddress(), ])); expect(await x2EarnRewardsPoolV1.version()).to.equal("1"); // update x2EarnApps address await x2EarnRewardsPoolV1.connect(owner).setX2EarnApps(await x2EarnApps.getAddress()); const x2EarnAppsAddress = await x2EarnRewardsPoolV1.x2EarnApps(); // deposit some funds const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app"), creator1); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app #2"), creator2); await b3tr.connect(owner).approve(await x2EarnRewardsPoolV1.getAddress(), amount); await x2EarnRewardsPoolV1.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPoolV1.getAddress())).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV2 = (await upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, config.X_2_EARN_INITIAL_IMPACT_KEYS], { version: 2, })); expect(await x2EarnRewardsPoolV2.version()).to.equal("2"); expect(await x2EarnRewardsPoolV2.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV2.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV3 = (await upgradeProxy("X2EarnRewardsPoolV2", "X2EarnRewardsPoolV3", await x2EarnRewardsPoolV1.getAddress(), [await veBetterPassport.getAddress()], { version: 3, })); expect(await x2EarnRewardsPoolV3.version()).to.equal("3"); expect(await x2EarnRewardsPoolV3.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV3.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV4 = (await upgradeProxy("X2EarnRewardsPoolV3", "X2EarnRewardsPoolV4", await x2EarnRewardsPoolV1.getAddress(), [], { version: 4, })); expect(await x2EarnRewardsPoolV4.version()).to.equal("4"); expect(await x2EarnRewardsPoolV4.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV4.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV5 = (await upgradeProxy("X2EarnRewardsPoolV4", "X2EarnRewardsPoolV5", await x2EarnRewardsPoolV1.getAddress(), [], { version: 5, })); expect(await x2EarnRewardsPoolV5.version()).to.equal("5"); expect(await x2EarnRewardsPoolV5.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV5.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV6 = (await upgradeProxy("X2EarnRewardsPoolV5", "X2EarnRewardsPoolV6", await x2EarnRewardsPoolV1.getAddress(), [], { version: 6, })); expect(await x2EarnRewardsPoolV6.version()).to.equal("6"); expect(await x2EarnRewardsPoolV6.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV6.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to V7 const x2EarnRewardsPoolV7 = (await upgradeProxy("X2EarnRewardsPoolV6", "X2EarnRewardsPoolV7", await x2EarnRewardsPoolV1.getAddress(), [], { version: 7, })); expect(await x2EarnRewardsPoolV7.version()).to.equal("7"); // upgrade to V8 const x2EarnRewardsPool = (await upgradeProxy("X2EarnRewardsPoolV7", "X2EarnRewardsPool", await x2EarnRewardsPoolV1.getAddress(), [], { version: 8, })); expect(await x2EarnRewardsPool.version()).to.equal("8"); expect(await x2EarnRewardsPool.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPool.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); }); it("Should be able to distribute rewards without providing metadata", async () => { const config = createLocalConfig(); const { owner, b3tr, x2EarnApps, minterAccount, veBetterPassport, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, config, }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await x2EarnApps.getAddress(), ])); expect(await x2EarnRewardsPoolV1.version()).to.equal("1"); // update x2EarnApps address await x2EarnRewardsPoolV1.connect(owner).setX2EarnApps(await x2EarnApps.getAddress()); const x2EarnAppsAddress = await x2EarnRewardsPoolV1.x2EarnApps(); // deposit some funds const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app"), creator1); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app #2"), creator2); await b3tr.connect(owner).approve(await x2EarnRewardsPoolV1.getAddress(), amount); await x2EarnRewardsPoolV1.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPoolV1.getAddress())).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV2 = (await upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, config.X_2_EARN_INITIAL_IMPACT_KEYS], { version: 2, })); expect(await x2EarnRewardsPoolV2.version()).to.equal("2"); expect(await x2EarnRewardsPoolV2.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV2.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV3 = (await upgradeProxy("X2EarnRewardsPoolV2", "X2EarnRewardsPoolV3", await x2EarnRewardsPoolV1.getAddress(), [await veBetterPassport.getAddress()], { version: 3, })); expect(await x2EarnRewardsPoolV3.version()).to.equal("3"); expect(await x2EarnRewardsPoolV3.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV3.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV4 = (await upgradeProxy("X2EarnRewardsPoolV3", "X2EarnRewardsPoolV4", await x2EarnRewardsPoolV1.getAddress(), [], { version: 4, })); expect(await x2EarnRewardsPoolV4.version()).to.equal("4"); expect(await x2EarnRewardsPoolV4.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV4.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV5 = (await upgradeProxy("X2EarnRewardsPoolV4", "X2EarnRewardsPoolV5", await x2EarnRewardsPoolV1.getAddress(), [], { version: 5, })); expect(await x2EarnRewardsPoolV5.version()).to.equal("5"); expect(await x2EarnRewardsPoolV5.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV5.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV6 = (await upgradeProxy("X2EarnRewardsPoolV5", "X2EarnRewardsPoolV6", await x2EarnRewardsPoolV1.getAddress(), [], { version: 6, })); expect(await x2EarnRewardsPoolV6.version()).to.equal("6"); expect(await x2EarnRewardsPoolV6.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV6.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to V7 const x2EarnRewardsPoolV7 = (await upgradeProxy("X2EarnRewardsPoolV6", "X2EarnRewardsPoolV7", await x2EarnRewardsPoolV1.getAddress(), [], { version: 7, })); expect(await x2EarnRewardsPoolV7.version()).to.equal("7"); // upgrade to V8 const x2EarnRewardsPool = (await upgradeProxy("X2EarnRewardsPoolV7", "X2EarnRewardsPool", await x2EarnRewardsPoolV1.getAddress(), [], { version: 8, })); expect(await x2EarnRewardsPool.version()).to.equal("8"); expect(await x2EarnRewardsPool.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPool.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); const user = otherAccounts[12]; await b3tr.connect(minterAccount).mint(owner.address, amount); const appId = await x2EarnApps.hashAppName("My app"); // add reward distributor await x2EarnApps.connect(owner).addRewardDistributor(appId, owner.address); expect(await x2EarnApps.isRewardDistributor(appId, owner.address)).to.equal(true); // distribute reward const tx = await x2EarnRewardsPool .connect(owner) .distributeRewardWithProof(appId, ethers.parseEther("1"), user.address, ["image"], ["https://image.png"], ["carbon", "water"], [100, 200], "The description of the action"); const receipt = await tx.wait(); expect(await b3tr.balanceOf(user.address)).to.equal(ethers.parseEther("1")); expect(await b3tr.balanceOf(await x2EarnRewardsPool.getAddress())).to.equal(ethers.parseEther("99")); // event emitted if (!receipt) throw new Error("No receipt"); let event = filterEventsByName(receipt.logs, "RewardDistributed"); expect(event).not.to.eql([]); expect(event[0].args[0]).to.equal(ethers.parseEther("1")); expect(event[0].args[1]).to.equal(appId); expect(event[0].args[2]).to.equal(user.address); const emittedProof = JSON.parse(event[0].args[3]); expect(emittedProof).to.have.property("version"); expect(emittedProof.version).to.equal(2); expect(emittedProof).to.have.deep.property("proof", { image: "https://image.png" }); expect(emittedProof).to.have.property("description"); expect(emittedProof.description).to.equal("The description of the action"); expect(emittedProof).to.have.deep.property("impact", { carbon: 100, water: 200 }); expect(event[0].args[4]).to.equal(owner.address); }); it("Should be able to distribute rewards providing metadata", async () => { const config = createLocalConfig(); const { owner, b3tr, x2EarnApps, minterAccount, veBetterPassport, otherAccounts } = await getOrDeployContractInstances({ forceDeploy: true, config, }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await x2EarnApps.getAddress(), ])); expect(await x2EarnRewardsPoolV1.version()).to.equal("1"); // update x2EarnApps address await x2EarnRewardsPoolV1.connect(owner).setX2EarnApps(await x2EarnApps.getAddress()); const x2EarnAppsAddress = await x2EarnRewardsPoolV1.x2EarnApps(); // deposit some funds const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app"), creator1); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app #2"), creator2); await b3tr.connect(owner).approve(await x2EarnRewardsPoolV1.getAddress(), amount); await x2EarnRewardsPoolV1.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPoolV1.getAddress())).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV2 = (await upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, config.X_2_EARN_INITIAL_IMPACT_KEYS], { version: 2, })); expect(await x2EarnRewardsPoolV2.version()).to.equal("2"); expect(await x2EarnRewardsPoolV2.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV2.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV3 = (await upgradeProxy("X2EarnRewardsPoolV2", "X2EarnRewardsPoolV3", await x2EarnRewardsPoolV1.getAddress(), [await veBetterPassport.getAddress()], { version: 3, })); expect(await x2EarnRewardsPoolV3.version()).to.equal("3"); expect(await x2EarnRewardsPoolV3.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV3.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV4 = (await upgradeProxy("X2EarnRewardsPoolV3", "X2EarnRewardsPoolV4", await x2EarnRewardsPoolV1.getAddress(), [], { version: 4, })); expect(await x2EarnRewardsPoolV4.version()).to.equal("4"); expect(await x2EarnRewardsPoolV4.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV4.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV5 = (await upgradeProxy("X2EarnRewardsPoolV4", "X2EarnRewardsPoolV5", await x2EarnRewardsPoolV1.getAddress(), [], { version: 5, })); expect(await x2EarnRewardsPoolV5.version()).to.equal("5"); expect(await x2EarnRewardsPoolV5.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV5.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to new version const x2EarnRewardsPoolV6 = (await upgradeProxy("X2EarnRewardsPoolV5", "X2EarnRewardsPoolV6", await x2EarnRewardsPoolV1.getAddress(), [], { version: 6, })); expect(await x2EarnRewardsPoolV6.version()).to.equal("6"); expect(await x2EarnRewardsPoolV6.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPoolV6.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); // upgrade to V7 const x2EarnRewardsPoolV7 = (await upgradeProxy("X2EarnRewardsPoolV6", "X2EarnRewardsPoolV7", await x2EarnRewardsPoolV1.getAddress(), [], { version: 7, })); expect(await x2EarnRewardsPoolV7.version()).to.equal("7"); // upgrade to V8 const x2EarnRewardsPool = (await upgradeProxy("X2EarnRewardsPoolV7", "X2EarnRewardsPool", await x2EarnRewardsPoolV1.getAddress(), [], { version: 8, })); expect(await x2EarnRewardsPool.version()).to.equal("8"); expect(await x2EarnRewardsPool.x2EarnApps()).to.equal(x2EarnAppsAddress); expect(await x2EarnRewardsPool.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); const user = otherAccounts[12]; await b3tr.connect(minterAccount).mint(owner.address, amount); const appId = await x2EarnApps.hashAppName("My app"); // add reward distributor await x2EarnApps.connect(owner).addRewardDistributor(appId, owner.address); expect(await x2EarnApps.isRewardDistributor(appId, owner.address)).to.equal(true); // distribute reward const tx = await x2EarnRewardsPool .connect(owner) .distributeRewardWithProofAndMetadata(appId, ethers.parseEther("1"), user.address, ["image"], ["https://image.png"], ["carbon", "water"], [100, 200], "The description of the action", '{"country":"Brazil"}'); const receipt = await tx.wait(); expect(await b3tr.balanceOf(user.address)).to.equal(ethers.parseEther("1")); expect(await b3tr.balanceOf(await x2EarnRewardsPool.getAddress())).to.equal(ethers.parseEther("99")); // event emitted if (!receipt) throw new Error("No receipt"); let event = filterEventsByName(receipt.logs, "RewardDistributed"); expect(event).not.to.eql([]); expect(event[0].args[0]).to.equal(ethers.parseEther("1")); expect(event[0].args[1]).to.equal(appId); expect(event[0].args[2]).to.equal(user.address); const emittedProof = JSON.parse(event[0].args[3]); expect(emittedProof).to.have.property("version"); expect(emittedProof.version).to.equal(2); expect(emittedProof).to.have.deep.property("proof", { image: "https://image.png" }); expect(emittedProof).to.have.property("description"); expect(emittedProof.description).to.equal("The description of the action"); expect(emittedProof).to.have.deep.property("impact", { carbon: 100, water: 200 }); let eventMetadata = filterEventsByName(receipt.logs, "RewardMetadata"); expect(eventMetadata).not.to.eql([]); expect(eventMetadata[0].args[0]).to.equal(ethers.parseEther("1")); expect(eventMetadata[0].args[1]).to.equal(appId); expect(eventMetadata[0].args[2]).to.equal(user.address); const emittedMetadata = JSON.parse(eventMetadata[0].args[3]); expect(emittedMetadata).to.have.deep.property("country", "Brazil"); expect(event[0].args[4]).to.equal(owner.address); }); it("Should not be able to upgrade if initial impact keys is empty", async () => { const config = createLocalConfig(); const { owner, b3tr, x2EarnApps, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, config, }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await x2EarnApps.getAddress(), ])); expect(await x2EarnRewardsPoolV1.version()).to.equal("1"); // update x2EarnApps address await x2EarnRewardsPoolV1.connect(owner).setX2EarnApps(await x2EarnApps.getAddress()); // deposit some funds const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app"), owner); await endorseApp(await x2EarnApps.hashAppName("My app #2"), minterAccount); await b3tr.connect(owner).approve(await x2EarnRewardsPoolV1.getAddress(), amount); await x2EarnRewardsPoolV1.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPoolV1.getAddress())).to.equal(amount); // upgrade to new version await expect(upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, []], { version: 2, })).to.be.reverted; await expect(upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, ["impact"]], { version: 2, })).to.not.be.reverted; }); it("Should not be able to upgrade to V3 if veBetterPassport address is empty", async () => { const config = createLocalConfig(); const { owner, b3tr, x2EarnApps, minterAccount, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, config, }); const x2EarnRewardsPoolV1 = (await deployProxy("X2EarnRewardsPoolV1", [ owner.address, owner.address, owner.address, await b3tr.getAddress(), await x2EarnApps.getAddress(), ])); expect(await x2EarnRewardsPoolV1.version()).to.equal("1"); // update x2EarnApps address await x2EarnRewardsPoolV1.connect(owner).setX2EarnApps(await x2EarnApps.getAddress()); // deposit some funds const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); await endorseApp(await x2EarnApps.hashAppName("My app"), owner); await endorseApp(await x2EarnApps.hashAppName("My app #2"), otherAccount); await b3tr.connect(owner).approve(await x2EarnRewardsPoolV1.getAddress(), amount); await x2EarnRewardsPoolV1.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPoolV1.getAddress())).to.equal(amount); // upgrade to new version await expect(upgradeProxy("X2EarnRewardsPoolV1", "X2EarnRewardsPoolV2", await x2EarnRewardsPoolV1.getAddress(), [owner.address, config.X_2_EARN_INITIAL_IMPACT_KEYS], { version: 2, })).to.not.be.reverted; await expect(upgradeProxy("X2EarnRewardsPoolV2", "X2EarnRewardsPool", await x2EarnRewardsPoolV1.getAddress(), [ZERO_ADDRESS], { version: 3, })).to.be.reverted; await expect(upgradeProxy("X2EarnRewardsPoolV2", "X2EarnRewardsPool", await x2EarnRewardsPoolV1.getAddress(), [owner.address], { version: 3, })).to.not.be.reverted; }); }); // settings describe("Settings", function () { it("CONTRACTS_ADDRESS_MANAGER_ROLE can set new x2EarnApps", async function () { const { x2EarnRewardsPool, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await x2EarnRewardsPool.hasRole(await x2EarnRewardsPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), owner.address)).to.eql(true); await x2EarnRewardsPool.connect(owner).setX2EarnApps(await otherAccount.getAddress()); expect(await x2EarnRewardsPool.x2EarnApps()).to.equal(await otherAccount.getAddress()); }); it("Only CONTRACTS_ADDRESS_MANAGER_ROLE can set new x2EarnApps", async function () { const { x2EarnRewardsPool, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true, }); expect(await x2EarnRewardsPool.hasRole(await x2EarnRewardsPool.CONTRACTS_ADDRESS_MANAGER_ROLE(), otherAccount.address)).to.eql(false); await catchRevert(x2EarnRewardsPool.connect(otherAccount).setX2EarnApps(await otherAccount.getAddress())); }); it("New x2EarnApps address cannot be the zero address", async function () { const { x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await catchRevert(x2EarnRewardsPool.connect(owner).setX2EarnApps(ZERO_ADDRESS)); }); it("Can't send VET to the contract", async function () { const { x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, }); await expect(owner.sendTransaction({ to: await x2EarnRewardsPool.getAddress(), value: ethers.parseEther("1.0"), // Sends exactly 1.0 ether })).to.be.reverted; const balance = await ethers.provider.getBalance(await x2EarnRewardsPool.getAddress()); expect(balance).to.equal(0n); }); it("Can't send ERC721 to the contract", async function () { const { myErc721, x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, deployMocks: true, }); if (!myErc721) throw new Error("No ERC721 contract"); await myErc721.connect(owner).safeMint(owner.address, 1); // @ts-ignore await expect(myErc721.connect(owner).safeTransferFrom(owner.address, await x2EarnRewardsPool.getAddress(), 1)).to .be.reverted; }); it("Cannot send ERC1155 to the contract", async function () { const { myErc1155, x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, deployMocks: true, }); if (!myErc1155) throw new Error("No ERC1155 contract"); await myErc1155.connect(owner).mint(owner.address, 1, 1, "0x"); // @ts-ignore await expect(myErc1155.connect(owner).safeTransferFrom(owner.address, await x2EarnRewardsPool.getAddress(), 1, 1, "0x")).to.be.reverted; }); it("Cannot batch send ERC1155 to the contract", async function () { const { myErc1155, x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: true, deployMocks: true, }); if (!myErc1155) throw new Error("No ERC1155 contract"); await expect(myErc1155.connect(owner).mintBatch(await x2EarnRewardsPool.getAddress(), [2, 3], [2, 3], new Uint8Array(0))).to.be.reverted; }); it("should revert when calling fallback function with call data", async function () { const { x2EarnRewardsPool, owner } = await getOrDeployContractInstances({ forceDeploy: false, }); owner.sendTransaction({ to: await x2EarnRewardsPool.getAddress(), value: 0, data: "0x1234", // some data }); }); it("Can get and set veBetterPassport address", async function () { const { x2EarnRewardsPool, owner, otherAccount } = await getOrDeployContractInstances({ forceDeploy: true }); await x2EarnRewardsPool.connect(owner).setVeBetterPassport(owner.address); const updatedVeBetterPassportAddress = await x2EarnRewardsPool.veBetterPassport(); expect(updatedVeBetterPassportAddress).to.eql(owner.address); // only admin can set the veBetterPassport address await expect(x2EarnRewardsPool.connect(otherAccount).setVeBetterPassport(otherAccount.address)).to.be.reverted; }); }); // deposit describe("Deposit", function () { // everyone can deposit it("Anyone can deposit", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); // create app await x2EarnApps.connect(creator1).submitApp(owner.address, owner.address, "My app", "metadataURI"); await x2EarnApps.submitApp(owner.address, owner.address, "My app #2", "metadataURI"); const appId1 = ethers.keccak256(ethers.toUtf8Bytes("My app")); const appId2 = ethers.keccak256(ethers.toUtf8Bytes("My app #2")); await endorseApp(appId1, owner); await endorseApp(appId2, minterAccount); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await b3tr.balanceOf(await x2EarnRewardsPool.getAddress())).to.equal(amount); }); // app must exist it("Should not allow deposit to non-existing app", async function () { const { x2EarnRewardsPool, b3tr, owner, x2EarnApps } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const amount = ethers.parseEther("100"); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await catchRevert(x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app"))); }); // owner must approve token it("Should not allow deposit without approval", async function () { const { x2EarnRewardsPool, x2EarnApps, owner } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const amount = ethers.parseEther("100"); await x2EarnApps.submitApp(owner.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await catchRevert(x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app"))); }); it("Should emit Deposit event", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(otherAccount.address, amount); await x2EarnApps.submitApp(owner.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(otherAccount).approve(await x2EarnRewardsPool.getAddress(), amount); const tx = await x2EarnRewardsPool.connect(otherAccount).deposit(amount, await x2EarnApps.hashAppName("My app")); const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let event = filterEventsByName(receipt.logs, "NewDeposit"); expect(event).not.to.eql([]); expect(event[0].args[0]).to.equal(amount); expect(event[0].args[1]).to.equal(await x2EarnApps.hashAppName("My app")); expect(event[0].args[2]).to.equal(otherAccount.address); }); }); describe("Balance", function () { it("Should return correct balance", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(owner.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); expect(await x2EarnRewardsPool.availableFunds(await x2EarnApps.hashAppName("My app"))).to.equal(amount); }); }); // withdraw describe("Withdraw", function () { it("The admin of the app can withdraw", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccounts[10]; const appAdmin = otherAccounts[11]; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(teamWallet.address, appAdmin.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, appId); expect(await b3tr.balanceOf(teamWallet.address)).to.equal(0n); await x2EarnRewardsPool.connect(appAdmin).withdraw(ethers.parseEther("1"), appId, ""); expect(await b3tr.balanceOf(await x2EarnRewardsPool.getAddress())).to.equal(ethers.parseEther("99")); // money are sent to team wallet expect(await b3tr.balanceOf(teamWallet.address)).to.equal(ethers.parseEther("1")); // can leave a reason const tx = await x2EarnRewardsPool .connect(appAdmin) .withdraw(ethers.parseEther("1"), await x2EarnApps.hashAppName("My app"), "For the team"); // "Should emit Withdraw event" const receipt = await tx.wait(); if (!receipt) throw new Error("No receipt"); let event = filterEventsByName(receipt.logs, "TeamWithdrawal"); expect(event).not.to.eql([]); expect(event[0].args[0]).to.equal(ethers.parseEther("1")); expect(event[0].args[1]).to.equal(await x2EarnApps.hashAppName("My app")); expect(event[0].args[2]).to.equal(teamWallet.address); expect(event[0].args[3]).to.equal(appAdmin.address); expect(event[0].args[4]).to.equal("For the team"); }); it("The app distributor cannot withdraw", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccounts, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccounts[10]; const appAdmin = otherAccounts[11]; const appDistributor = otherAccounts[12]; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(teamWallet.address, appAdmin.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await x2EarnApps.connect(appAdmin).addRewardDistributor(appId, appDistributor.address); expect(await x2EarnApps.isRewardDistributor(appId, appDistributor.address)).to.equal(true); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, appId); expect(await b3tr.balanceOf(teamWallet.address)).to.equal(0n); await expect(x2EarnRewardsPool.connect(appDistributor).withdraw(ethers.parseEther("1"), appId, "")).to.be.revertedWith("X2EarnRewardsPool: not an app admin"); }); it("App must exist", async function () { const { x2EarnRewardsPool, b3tr, owner, x2EarnApps } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), ethers.parseEther("100")); await catchRevert(x2EarnRewardsPool.connect(owner).withdraw(ethers.parseEther("1"), await x2EarnApps.hashAppName("My app"), "")); }); it("Normal users cannot withdraw", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccount; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(teamWallet.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); await catchRevert(x2EarnRewardsPool .connect(otherAccount) .withdraw(ethers.parseEther("1"), await x2EarnApps.hashAppName("My app"), "")); }); it("Cannot withdraw as admin of another app", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccount; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.connect(creator1).submitApp(teamWallet.address, owner.address, "My app", "metadataURI"); await x2EarnApps.connect(creator2).submitApp(owner.address, owner.address, "My app #2", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); const app2Id = ethers.keccak256(ethers.toUtf8Bytes("My app #2")); await endorseApp(appId, owner); await endorseApp(app2Id, minterAccount); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); await catchRevert(x2EarnRewardsPool .connect(teamWallet) .withdraw(ethers.parseEther("1"), await x2EarnApps.hashAppName("My app"), "")); }); it("App must have enough available funds to withdraw", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccount; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(teamWallet.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); await catchRevert(x2EarnRewardsPool.connect(owner).withdraw(ethers.parseEther("101"), await x2EarnApps.hashAppName("My app"), "")); }); it("Should not allow to withdraw more than available funds", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccount, minterAccount } = await getOrDeployContractInstances({ forceDeploy: true, bootstrapAndStartEmissions: true, }); const teamWallet = otherAccount; const amount = ethers.parseEther("100"); await b3tr.connect(minterAccount).mint(owner.address, amount); await x2EarnApps.submitApp(teamWallet.address, owner.address, "My app", "metadataURI"); const appId = ethers.keccak256(ethers.toUtf8Bytes("My app")); await endorseApp(appId, owner); await b3tr.connect(owner).approve(await x2EarnRewardsPool.getAddress(), amount); await x2EarnRewardsPool.connect(owner).deposit(amount, await x2EarnApps.hashAppName("My app")); await catchRevert(x2EarnRewardsPool.connect(owner).withdraw(ethers.parseEther("101"), await x2EarnApps.hashAppName("My app"), "")); }); }); // distributeRewards describe("Distribute rewards", async function () { it("Selected address from team can distribute rewards", async function () { const { x2EarnRewardsPool, x2EarnApps, b3tr, owner, otherAccounts, minterAccount } = await getOrDeployCo