@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
238 lines (200 loc) • 12.9 kB
text/typescript
import { assert, expect } from "chai";
import { ethers } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { Protocol, protocolFixture, RocketPool, SetupData } from "./test";
import { BigNumber } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
import { expectNumberE18ToBeApproximately, getEventNames, prepareOperatorDistributionContract, registerNewValidator, upgradePriceFetcherToMock, whitelistUserServerSig } from "./utils/utils";
import { ContractTransaction } from "@ethersproject/contracts";
import { wEth } from "../typechain-types/factories/contracts/Testing";
import { MerkleClaimStreamer, RocketMerkleDistributorMainnet, RocketVault } from "../typechain-types";
describe("Merkle Claim Streamer", async () => {
let setupData: SetupData;
let protocol: Protocol;
let admin: SignerWithAddress;
let nonAdmin: SignerWithAddress;
let mcs: MerkleClaimStreamer;
let rocketpool: RocketPool;
let rocketVault: RocketVault;
let rocketMDM: RocketMerkleDistributorMainnet;
beforeEach(async () => {
setupData = await loadFixture(protocolFixture);
protocol = setupData.protocol;
admin = setupData.signers.admin;
nonAdmin = setupData.signers.random;
mcs = protocol.merkleClaimStreamer;
rocketpool = setupData.rocketPool;
rocketMDM = await ethers.getContractAt("RocketMerkleDistributorMainnet", await protocol.directory.getRocketMerkleDistributorMainnetAddress())
await rocketMDM.useMock();
rocketVault = await ethers.getContractAt("RocketVault", await rocketpool.rockStorageContract.getAddress(await rocketMDM.rocketVaultKey()));
await rocketVault.useMock();
})
describe("streaming interval is changed", async () => {
it("can be changed by admin to something above zero and up to 365 days", async () => {
await expect(mcs.connect(admin).setStreamingInterval(69)).to.not.be.reverted;
expect(await mcs.streamingInterval()).equals(69)
await expect(mcs.connect(admin).setStreamingInterval(31536000)).to.not.be.reverted;
expect(await mcs.streamingInterval()).equals(31536000)
await expect(mcs.connect(admin).setStreamingInterval(1)).to.not.be.reverted;
expect(await mcs.streamingInterval()).equals(1)
})
it("cannot be set out of bounds", async () => {
await expect(mcs.connect(admin).setStreamingInterval(0)).to.be.revertedWith("New streaming interval must be > 0 seconds and <= 365 days");
expect(await mcs.streamingInterval()).not.equals(0)
await expect(mcs.connect(admin).setStreamingInterval(31536001)).to.be.revertedWith("New streaming interval must be > 0 seconds and <= 365 days");
expect(await mcs.streamingInterval()).not.equals(31536001)
})
it("cannot be changed by nonAdmin", async () => {
await expect(mcs.connect(nonAdmin).setStreamingInterval(69)).to.be.revertedWith("Can only be called by admin address!");
expect(await mcs.streamingInterval()).not.equals(69)
await expect(mcs.connect(nonAdmin).setStreamingInterval(31536000)).to.be.revertedWith("Can only be called by admin address!");
expect(await mcs.streamingInterval()).not.equals(31536000)
await expect(mcs.connect(nonAdmin).setStreamingInterval(1)).to.be.revertedWith("Can only be called by admin address!");
expect(await mcs.streamingInterval()).not.equals(1)
})
it.skip("can submit merkle claim after changing streaming interval time", async () => {
await assertMerkleClaim(rocketVault, setupData, ethers.utils.parseEther("1"),ethers.utils.parseEther("1"), 69);
await expect(mcs.connect(admin).setStreamingInterval(6969)).to.not.be.reverted;
expect(await mcs.streamingInterval()).equals(6969)
await assertMerkleClaim(rocketVault, setupData, ethers.utils.parseEther("1"),ethers.utils.parseEther("1"), 69);
})
})
describe("when claims are disabled", async () => {
beforeEach(async () => {
await expect(await mcs.connect(admin).setMerkleClaimsEnabled(false)).to.not.be.reverted;
expect(await mcs.merkleClaimsEnabled()).equals(false);
})
it("cannot submit claim", async () => {
const rewardIndex = [0];
const amountRpl = [ethers.utils.parseEther("100")];
const amountEth = [ethers.utils.parseEther("100")];
const proof = [[ethers.utils.hexZeroPad("0x0", 32)], [ethers.utils.hexZeroPad("0x0", 32)]]
expect(mcs.submitMerkleClaim([rewardIndex], amountRpl, amountEth, proof)).to.be.revertedWith("Merkle claims are disabled");
});
it("only admin can re-enable", async () => {
await expect(mcs.connect(nonAdmin).setMerkleClaimsEnabled(true)).to.be.revertedWith('Can only be called by admin address!');
})
it("can submit claim if re-enabled", async () => {
await mcs.connect(admin).setMerkleClaimsEnabled(true);
expect(await mcs.merkleClaimsEnabled()).equals(true);
await assertMerkleClaim(rocketVault, setupData, ethers.utils.parseEther("1"), ethers.utils.parseEther("1"), 1);
});
})
describe("when claims are enabled", async () => {
beforeEach(async () => {
expect(await mcs.merkleClaimsEnabled()).equals(true);
expect(await protocol.vCRPL.totalAssets()).equals(0);
expect(await protocol.vCWETH.totalAssets()).equals(0);
})
it("only admin can disable", async () => {
await expect(mcs.connect(nonAdmin).setMerkleClaimsEnabled(false)).to.be.revertedWith('Can only be called by admin address!');
await expect(mcs.connect(admin).setMerkleClaimsEnabled(false)).to.not.be.reverted;
expect(await mcs.merkleClaimsEnabled()).equals(false);
})
describe("when eth and rpl amount are both 0", async () => {
it("cannot submit claim", async () => {
rocketMDM.disableMock();
const proof = [[ethers.utils.hexZeroPad("0x0", 32)], [ethers.utils.hexZeroPad("0x0", 32)]]
await expect(protocol.merkleClaimStreamer.submitMerkleClaim([0], [0], [0], proof)).to.be.revertedWith("Invalid amount")
})
})
describe("when either eth or rpl amount is above 0", async () => {
type ParamsType = { ethRewards: number; rplRewards: number; waitTime: number};
[
{ ethRewards: 28, rplRewards: 28, waitTime: 86400},
{ ethRewards: .6, rplRewards: .4, waitTime: 1},
{ ethRewards: .1, rplRewards: .1, waitTime: 86400}, // 1 day in seconds
{ ethRewards: .5, rplRewards: .4, waitTime: 31556952000}, // 1000 years
{ ethRewards: .99, rplRewards: .01, waitTime: 2419200}, // 28 days in seconds
{ ethRewards: .99999, rplRewards: .00001, waitTime: 259200 }, // 3 days in seconds
{ ethRewards: 1, rplRewards: 0, waitTime: 5 },
{ ethRewards: 0, rplRewards: 1, waitTime: 69 },
{ ethRewards: 69, rplRewards: 1, waitTime: 0 },
{ ethRewards: 0, rplRewards: 69, waitTime: 0 },
].forEach((params: ParamsType) => {
let rplRewards = ethers.utils.parseEther(`${params.rplRewards}`)
let ethRewards = ethers.utils.parseEther(`${params.ethRewards}`)
it(`can submit claim with ethRewards=${ethRewards}, rplRewards=${rplRewards}, waitTime=${params.waitTime}`, async () => {
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, params.waitTime);
});
it.skip(`can submit multiple claims with ethRewards=${ethRewards}, rplRewards=${rplRewards}, waitTime=${params.waitTime}`, async () => {
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, params.waitTime);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, params.waitTime);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, params.waitTime);
})
it.skip(`can submit multiple claims in the same block with ethRewards=${ethRewards}, rplRewards=${rplRewards}, waitTime=${params.waitTime}`, async () => {
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 0);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 0);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 0);
})
it.skip(`can submit multiple claims with different wait times and ethRewards=${ethRewards}, rplRewards=${rplRewards}}`, async () => {
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 69);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 2419200);
await assertMerkleClaim(rocketVault, setupData, ethRewards, rplRewards, 31556952000);
})
})
})
})
});
async function assertMerkleClaim(rocketVault: RocketVault, setupData: SetupData, ethRewards: BigNumber, rplRewards: BigNumber, waitTime: number ) {
try {
let protocol = setupData.protocol;
let rocketpool = setupData.rocketPool;
let mcs = protocol.merkleClaimStreamer;
// send rewards to merkle distributor
await rocketpool.rplContract.connect(setupData.signers.rplWhale).transfer(rocketVault.address, rplRewards);
await setupData.signers.ethWhale.sendTransaction({
to: rocketVault.address,
value: ethRewards
})
const rewardIndex = [0];
const proof = [[ethers.utils.hexZeroPad("0x0", 32)], [ethers.utils.hexZeroPad("0x0", 32)]]
let originalTotalAssetsEth = await protocol.vCWETH.totalAssets();
let originalTotalAssetsRpl = await protocol.vCRPL.totalAssets();
const streamingInterval = await mcs.streamingInterval()
const expectedTreasuryPortionEth = await protocol.vCWETH.getTreasuryPortion(ethRewards);
const expectedTreasuryPortionRpl = await protocol.vCRPL.getTreasuryPortion(rplRewards);
const expectedOperatorPortionEth = await protocol.vCWETH.getOperatorPortion(ethRewards);
const expectedCommunityPortionEth = ethRewards.sub(expectedTreasuryPortionEth).sub(expectedOperatorPortionEth);
const expectedCommunityPortionRpl = rplRewards.sub(expectedTreasuryPortionRpl);
//disable auto-mine of new blocks
await ethers.provider.send("evm_setAutomine", [false]);
let claimTimestamp = (await ethers.provider.getBlock("latest")).timestamp+1
let claimTx = mcs.submitMerkleClaim(rewardIndex, [rplRewards], [ethRewards], proof)
// no time has passed, so no tvl has been streamed
expect(await protocol.vCRPL.totalAssets()).equals(originalTotalAssetsRpl);
expect(await protocol.vCWETH.totalAssets()).equals(originalTotalAssetsEth);
// mine a block with the merkle claim transaction above
await ethers.provider.send("evm_mine", [claimTimestamp]);
await expect(claimTx).to.not.be.revertedWith("");
const tx = await ((await claimTx).wait())
expect((await ethers.provider.getBlock(tx.blockHash)).timestamp).equals(claimTimestamp)
expect((await ethers.provider.getBlock("latest")).timestamp).equals(claimTimestamp);
expect(await mcs.queryFilter(mcs.filters.MerkleClaimSubmitted(claimTimestamp))).not.equals([]);
// todo: these don't work
//expect(claimTx).to.emit(mcs, "ASDF");
// expect(claimTx).to.emit(mcs, "MerkleClaimSubmitted")
// .withArgs(claimTimestamp, ethRewards, rplRewards, expectedTreasuryPortionEth, expectedOperatorPortionEth, expectedTreasuryPortionRpl)
console.log("claimTimestamp", claimTimestamp)
console.log("lastclaimtime()", await mcs.lastClaimTime())
expect(await mcs.lastClaimTime()).equals(claimTimestamp);
expect(await rocketpool.rplContract.balanceOf(protocol.vCRPL.address)).equals(0);
expect(await rocketpool.rplContract.balanceOf(protocol.operatorDistributor.address)).equals(0);
if(waitTime > 0) {
// mine another block to wait some time
await ethers.provider.send("evm_mine", [waitTime + claimTimestamp]);
}
let expectedStreamedTVLEth = streamingInterval.gt(waitTime) ? expectedCommunityPortionEth.mul(waitTime).div(streamingInterval) : expectedCommunityPortionEth;
let expectedStreamedTVLRpl = streamingInterval.gt(waitTime) ? expectedCommunityPortionRpl.mul(waitTime).div(streamingInterval) : expectedCommunityPortionRpl;
let expectedTotalAssetsEth = originalTotalAssetsEth.add(expectedStreamedTVLEth);
let expectedTotalAssetsRpl = originalTotalAssetsRpl.add(expectedStreamedTVLRpl);
expect(await mcs.priorEthStreamAmount()).equals(expectedCommunityPortionEth);
expect(await mcs.priorRplStreamAmount()).equals(expectedCommunityPortionRpl);
expect(await mcs.getStreamedTvlEth()).equals(expectedStreamedTVLEth);
expect(await mcs.getStreamedTvlRpl()).equals(expectedStreamedTVLRpl);
expect(await protocol.vCWETH.totalAssets()).equals(expectedTotalAssetsEth);
expect(await protocol.vCRPL.totalAssets()).equals(expectedTotalAssetsRpl);
}
// re-enable automine
finally { await ethers.provider.send("evm_setAutomine", [true]); }
}