@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
JavaScript
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