@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
274 lines (212 loc) • 15.4 kB
text/typescript
import { expect } from "chai";
import { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { protocolFixture, SetupData } from "./test";
import { BigNumber } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { Treasury, Directory, MockERC20 } from "../typechain-types"; // Adjust this import according to your project's structure
import { IERC20 } from "../typechain-types/oz-contracts-3-4-0/token/ERC20";
describe("treasury", function () {
let treasury: Treasury;
let treasurer: SignerWithAddress;
let nonTreasurer: SignerWithAddress;
let token: MockERC20;
let setupData: SetupData;
beforeEach(async function () {
setupData = await loadFixture(protocolFixture);
treasurer = setupData.signers.treasurer;
nonTreasurer = setupData.signers.random;
const MockERC20 = await ethers.getContractFactory("MockERC20");
token = await MockERC20.deploy("Mock Token", "MTKN", ethers.utils.parseEther("1000"));
await token.deployed();
const Treasury = await ethers.getContractFactory("Treasury");
treasury = await upgrades.deployProxy(
Treasury,
[treasurer.address],
{ kind: 'uups', unsafeAllow: ["constructor"], initializer: "initialize" }
) as Treasury;
await treasury.deployed();
expect(await treasury.hasRole(treasury.TREASURER_ROLE(), treasurer.address)).equals(true);
});
describe("claimToken", function () {
it("success - should allow treasurer to claim all tokens", async function () {
const totalSupply = await token.totalSupply();
await token.transfer(treasury.address, totalSupply);
await expect(treasury.connect(setupData.signers.treasurer).claimToken(token.address, setupData.signers.treasurer.address)).to.emit(treasury, "ClaimedToken").withArgs(token.address, treasurer.address, totalSupply);
const treasuryBalance = await token.balanceOf(setupData.signers.treasurer.address);
expect(treasuryBalance).to.equal(totalSupply);
});
it("success - treasurer can partially claim tokens in contract", async () => {
const totalSupply = await token.totalSupply();
const decimals = await token.decimals();
await token.transfer(treasury.address, totalSupply);
await expect(treasury.connect(setupData.signers.treasurer).claimTokenAmount(token.address, setupData.signers.treasurer.address, ethers.utils.parseUnits("1000", decimals))).to.emit(treasury, "ClaimedToken").withArgs(token.address, treasurer.address, ethers.utils.parseUnits("1000", decimals));
const treasuryBalance = await token.balanceOf(treasurer.address);
expect(treasuryBalance).to.equal(ethers.utils.parseUnits("1000", decimals));
expect(await token.balanceOf(treasury.address)).to.equal(totalSupply.sub(ethers.utils.parseUnits("1000", decimals)));
})
it("fail - non-treasurer claim tokens", async function () {
await token.transfer(treasury.address, ethers.utils.parseEther("100"));
await expect(treasury.connect(nonTreasurer).claimToken(token.address, nonTreasurer.address)).to.be.revertedWith("Can only be called by treasurer address!");
});
});
describe("test claimEth", () => {
it('success - treasurer claims all eth', async () => {
expect(await ethers.provider.getBalance(treasury.address)).equals(0)
await nonTreasurer.sendTransaction({
to: treasury.address,
value: ethers.utils.parseEther("1")
})
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther("1"))
await expect(treasury.connect(treasurer).claimEth(treasurer.address)).to.emit(treasury, "ClaimedEth").withArgs(treasurer.address, ethers.utils.parseEther("1"));
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther("0"))
})
it('success - treasury claims part of the eth', async () => {
expect(await ethers.provider.getBalance(treasury.address)).equals(0)
await nonTreasurer.sendTransaction({
to: treasury.address,
value: ethers.utils.parseEther("1")
})
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther("1"))
await expect(treasury.connect(treasurer).claimEthAmount(treasurer.address, ethers.utils.parseEther("0.1"))).to.emit(treasury, "ClaimedEth").withArgs(treasurer.address, ethers.utils.parseEther("0.1"));
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther(".9"))
})
it('fail - non-treasury claims all eth', async () => {
expect(await ethers.provider.getBalance(treasury.address)).equals(0)
await nonTreasurer.sendTransaction({
to: treasury.address,
value: ethers.utils.parseEther("1")
})
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther("1"))
await expect(treasury.connect(nonTreasurer).claimEth(nonTreasurer.address)).to.be.rejectedWith("Can only be called by treasurer address!");
expect(await ethers.provider.getBalance(treasury.address)).equals(ethers.utils.parseEther("1"))
})
})
describe("arbitrary execution", async () => {
it("success - treasurer can execute single target", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const encoding = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
expect(await mockTargetAlpha.called()).equals(0);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address], [encoding], [0])).to.emit(treasury, "Executed").withArgs(mockTargetAlpha.address, encoding);
expect(await mockTargetAlpha.called()).equals(69);
})
it("success - treasurer can execute single payable target", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const encoding = mockTargetAlpha.interface.encodeFunctionData("doCall", [6969]);
expect(await ethers.provider.getBalance(mockTargetAlpha.address)).equals(ethers.utils.parseEther("0"))
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address], [encoding], [ethers.utils.parseEther("1")], {
value: ethers.utils.parseEther("1")
})).to.emit(treasury, "Executed").withArgs(mockTargetAlpha.address, encoding);
expect(await ethers.provider.getBalance(mockTargetAlpha.address)).equals(ethers.utils.parseEther("1"))
})
it("fail - non-treasurer cannot execute single target", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const encoding = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
await expect(treasury.executeAll([mockTargetAlpha.address], [encoding], [0])).to.be.revertedWith("Can only be called by treasurer address!")
})
it("success - treasurer can execute many targets", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const mockTargetBravo = await MockTargetAlpha.deploy();
await mockTargetBravo.deployed();
const mockTargetCharlie = await MockTargetAlpha.deploy();
await mockTargetCharlie.deployed();
const encodingAlpha = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
const encodingBravo = mockTargetAlpha.interface.encodeFunctionData("doCall", [420]);
const encodingCharlie = mockTargetAlpha.interface.encodeFunctionData("doCall", [314159268]);
expect(await mockTargetAlpha.called()).equals(0);
expect(await mockTargetBravo.called()).equals(0);
expect(await mockTargetCharlie.called()).equals(0);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, 0])).to.emit(treasury, "Executed").withArgs(mockTargetAlpha.address, encodingAlpha);
expect(await mockTargetAlpha.called()).equals(69);
expect(await mockTargetBravo.called()).equals(420);
expect(await mockTargetCharlie.called()).equals(314159268);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, 0])).to.emit(treasury, "Executed").withArgs(mockTargetBravo.address, encodingBravo);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, 0])).to.emit(treasury, "Executed").withArgs(mockTargetCharlie.address, encodingCharlie);
})
it("success - treasurer can execute many payable targets", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const mockTargetBravo = await MockTargetAlpha.deploy();
await mockTargetBravo.deployed();
const mockTargetCharlie = await MockTargetAlpha.deploy();
await mockTargetCharlie.deployed();
const encodingAlpha = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
const encodingBravo = mockTargetAlpha.interface.encodeFunctionData("doCall", [420]);
const encodingCharlie = mockTargetAlpha.interface.encodeFunctionData("doCall", [314159268]);
expect(await ethers.provider.getBalance(mockTargetAlpha.address)).equals(ethers.utils.parseEther("0"))
expect(await ethers.provider.getBalance(mockTargetBravo.address)).equals(ethers.utils.parseEther("0"))
expect(await ethers.provider.getBalance(mockTargetCharlie.address)).equals(ethers.utils.parseEther("0"))
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [ethers.utils.parseEther("1"), 0, 0], {
value: ethers.utils.parseEther("1")
})).to.emit(treasury, "Executed").withArgs(mockTargetAlpha.address, encodingAlpha);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, ethers.utils.parseEther("2"), 0], {
value: ethers.utils.parseEther("2")
})).to.emit(treasury, "Executed").withArgs(mockTargetBravo.address, encodingBravo);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, ethers.utils.parseEther("3")], {
value: ethers.utils.parseEther("3")
})).to.emit(treasury, "Executed").withArgs(mockTargetCharlie.address, encodingCharlie);
expect(await ethers.provider.getBalance(mockTargetAlpha.address)).equals(ethers.utils.parseEther("1"))
expect(await ethers.provider.getBalance(mockTargetBravo.address)).equals(ethers.utils.parseEther("2"))
expect(await ethers.provider.getBalance(mockTargetCharlie.address)).equals(ethers.utils.parseEther("3"))
})
it("fail - non-treasurer can execute many targets", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const mockTargetBravo = await MockTargetAlpha.deploy();
await mockTargetBravo.deployed();
const mockTargetCharlie = await MockTargetAlpha.deploy();
await mockTargetCharlie.deployed();
const encodingAlpha = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
const encodingBravo = mockTargetAlpha.interface.encodeFunctionData("doCall", [420]);
const encodingCharlie = mockTargetAlpha.interface.encodeFunctionData("doCall", [314159268]);
await expect(treasury.executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, 0])).to.be.revertedWith("Can only be called by treasurer address!")
})
it("fail - treasurer calls executeAll with varying param lengths", async () => {
const MockTargetAlpha = await ethers.getContractFactory("MockTargetAlpha");
const mockTargetAlpha = await MockTargetAlpha.deploy();
await mockTargetAlpha.deployed();
const mockTargetBravo = await MockTargetAlpha.deploy();
await mockTargetBravo.deployed();
const mockTargetCharlie = await MockTargetAlpha.deploy();
await mockTargetCharlie.deployed();
const encodingAlpha = mockTargetAlpha.interface.encodeFunctionData("doCall", [69]);
const encodingBravo = mockTargetAlpha.interface.encodeFunctionData("doCall", [420]);
const encodingCharlie = mockTargetAlpha.interface.encodeFunctionData("doCall", [314159268]);
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0])).to.be.revertedWith("Treasury: array length mismatch.")
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address, mockTargetCharlie.address], [encodingAlpha, encodingBravo], [0, 0, 0])).to.be.revertedWith("Treasury: array length mismatch.")
await expect(treasury.connect(treasurer).executeAll([mockTargetAlpha.address, mockTargetBravo.address], [encodingAlpha, encodingBravo, encodingCharlie], [0, 0, 0])).to.be.revertedWith("Treasury: array length mismatch.")
})
})
describe("test upgrades", () => {
it("success - should upgrade", async () => {
const { protocol, signers } = setupData;
const MockTreasuryV2 = await ethers.getContractFactory("MockTreasuryV2");
const mockTreasuryV2 = await MockTreasuryV2.deploy();
await mockTreasuryV2.deployed();
const v2 = await ethers.getContractAt("MockTreasuryV2", treasury.address);
await expect(v2.test()).to.be.rejectedWith("CALL_EXCEPTION")
await treasury.connect(signers.treasurer).upgradeTo(mockTreasuryV2.address)
expect(await v2.test()).equals(69)
})
it("fail - non-treasurer cannot upgrade", async () => {
const { protocol, signers } = setupData;
const MockTreasuryV2 = await ethers.getContractFactory("MockTreasuryV2");
const mockTreasuryV2 = await MockTreasuryV2.deploy();
await mockTreasuryV2.deployed();
const v2 = await ethers.getContractAt("MockTreasuryV2", treasury.address);
await expect(v2.test()).to.be.rejectedWith("CALL_EXCEPTION")
await expect(treasury.connect(signers.admin).upgradeTo(mockTreasuryV2.address)).to.be.revertedWith("Upgrading only allowed by treasurer!")
await expect(v2.test()).to.be.rejectedWith("CALL_EXCEPTION")
})
})
});