Skip to content

Commit

Permalink
test: staking integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed May 23, 2024
1 parent 1daa8ab commit 9d13d87
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 24 deletions.
3 changes: 3 additions & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ module.exports = {
version: "0.6.6", // uniswap
}
]
},
gasReporter: {

Check failure on line 49 in hardhat.config.js

View workflow job for this annotation

GitHub Actions / build

Duplicate key 'gasReporter'
enabled: true
}
};
2 changes: 1 addition & 1 deletion lib/autonolas-governance
Submodule autonolas-governance updated 29 files
+6 −3 .solcover.js
+113 −19 abis/0.8.25/VoteWeighting.json
+22 −0 audits/internal12/README.md
+809 −0 audits/internal12/analysis2/contracts/VoteWeighting-flatten.sol
+20 −0 audits/internal12/analysis2/contracts/script.sh
+ audits/internal12/analysis2/slither_VoteWeighting-flatten.sol.IDispenser.call-graph.png
+ audits/internal12/analysis2/slither_VoteWeighting-flatten.sol.IVEOLAS.call-graph.png
+ audits/internal12/analysis2/slither_VoteWeighting-flatten.sol.VoteWeighting.call-graph.png
+ audits/internal12/analysis2/slither_VoteWeighting-flatten.sol.all_contracts.call-graph.png
+ audits/internal12/analysis2/slither_VoteWeighting-flatten.sol.inheritance-graph.png
+8 −0 audits/internal12/analysis2/slither_call-graph.txt
+31 −0 audits/internal12/analysis2/slither_constructor-calls.txt
+46 −0 audits/internal12/analysis2/slither_contract-summary.txt
+1,032 −0 audits/internal12/analysis2/slither_data-dependency.txt
+90 −0 audits/internal12/analysis2/slither_full.txt
+111 −0 audits/internal12/analysis2/slither_function-summary.txt
+22 −0 audits/internal12/analysis2/slither_human-summary.txt
+5 −0 audits/internal12/analysis2/slither_inheritance-graph.txt
+21 −0 audits/internal12/analysis2/slither_inheritance.txt
+55 −0 audits/internal12/analysis2/slither_modifiers.txt
+55 −0 audits/internal12/analysis2/slither_require.txt
+37 −0 audits/internal12/analysis2/slither_variable-order.txt
+56 −0 audits/internal12/analysis2/slither_vars-and-auth.txt
+84 −68 contracts/VoteWeighting.sol
+21 −11 test/OLAS.js
+29 −24 test/VoteWeighting.js
+66 −24 test/buOLAS.js
+38 −12 test/veOLAS.js
+3 −2 test/wveOLAS.js
2 changes: 1 addition & 1 deletion lib/autonolas-tokenomics
204 changes: 182 additions & 22 deletions test/StakingIncentives.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ describe("StakingIncentives", async () => {
const chainId = 31337;
const gnosisChainId = 100;
const defaultWeight = 1000;

Check warning on line 16 in test/StakingIncentives.js

View workflow job for this annotation

GitHub Actions / build

'defaultWeight' is assigned a value but never used
const maxWeight = 10000;
const numClaimedEpochs = 1;
const bridgePayload = "0x";
const epochLen = oneMonth;
const delta = 100;
const maxNumClaimingEpochs = 10;
const maxNumStakingTargets = 100;
const maxUint256 = ethers.constants.MaxUint256;
const numInstances = 3;
const defaultGasLimit = "2000000";

Check warning on line 24 in test/StakingIncentives.js

View workflow job for this annotation

GitHub Actions / build

'defaultGasLimit' is assigned a value but never used
const retainer = "0x" + "0".repeat(24) + "5".repeat(40);
const livenessRatio = "1" + "0".repeat(16); // 0.01 transaction per second (TPS)
let serviceParams = {
metadataHash: defaultHash,
maxNumServices: 3,
rewardsPerSecond: "1" + "0".repeat(15),
rewardsPerSecond: "1" + "0".repeat(18),
minStakingDeposit: 10,
minNumStakingPeriods: 3,
maxNumInactivityPeriods: 3,
livenessPeriod: 10, // Ten seconds
timeForEmissions: 100,
timeForEmissions: 100000000,
numAgentInstances: 1,
agentIds: [],
threshold: 0,
Expand All @@ -41,7 +41,10 @@ describe("StakingIncentives", async () => {
serviceRegistry: AddressZero,
activityChecker: AddressZero
};
const maxInactivity = serviceParams.maxNumInactivityPeriods * serviceParams.livenessPeriod + 1;
// Double (fload) delta
const dDelta = 1. / 10**10;
// Big number delta
const bnDelta = 10**7;

let signers;
let deployer;
Expand All @@ -51,7 +54,9 @@ describe("StakingIncentives", async () => {
let stakingActivityChecker;
let stakingFactory;
let stakingTokenImplementation;
let stakingInstance;
let stakingInstances = new Array(numInstances);
let stakingInstanceAddresses = new Array(numInstances);
let stakingInstanceAddresses32 = new Array(numInstances);
let tokenomics;
let treasury;
let dispenser;
Expand All @@ -61,6 +66,16 @@ describe("StakingIncentives", async () => {
let gnosisTargetDispenserL2;
let wormholeDepositProcessorL1;

Check warning on line 67 in test/StakingIncentives.js

View workflow job for this annotation

GitHub Actions / build

'wormholeDepositProcessorL1' is defined but never used

function compare(a, b) {
if (a.toString() < b.toString()){
return -1;
}
if (a.toString() > b.toString()){
return 1;
}
return 0;
}

function convertAddressToBytes32(account) {
return ("0x" + "0".repeat(24) + account.slice(2)).toLowerCase();
}
Expand Down Expand Up @@ -112,9 +127,16 @@ describe("StakingIncentives", async () => {
// Note serviceRegistryTokenUtility is also irrelevant for this testing (substituting with deployer)
const initPayload = stakingTokenImplementation.interface.encodeFunctionData("initialize",
[serviceParams, deployer.address, olas.address]);
const stakingTokenAddress = await stakingFactory.getProxyAddress(stakingTokenImplementation.address);
await stakingFactory.createStakingInstance(stakingTokenImplementation.address, initPayload);
stakingInstance = await ethers.getContractAt("StakingToken", stakingTokenAddress);
// Create staking proxy instances
for (let i = 0; i < numInstances; i++) {
const stakingTokenAddress = await stakingFactory.getProxyAddress(stakingTokenImplementation.address);
await stakingFactory.createStakingInstance(stakingTokenImplementation.address, initPayload);
const stakingInstance = await ethers.getContractAt("StakingToken", stakingTokenAddress);
stakingInstances[i] = stakingInstance;
stakingInstanceAddresses[i] = stakingTokenAddress;
stakingInstanceAddresses32[i] = convertAddressToBytes32(stakingTokenAddress);
}


// Tokenomics contracts
const Dispenser = await ethers.getContractFactory("Dispenser");
Expand Down Expand Up @@ -190,14 +212,14 @@ describe("StakingIncentives", async () => {
});

context("Staking incentives", async function () {
it("Claim staking incentives for a single nominee", async () => {
it.only("Claim staking incentives with total veOLAS power bigger than inflation", async () => {
// Take a snapshot of the current state of the blockchain
const snapshot = await helpers.takeSnapshot();

// Set staking fraction to 100%
await tokenomics.changeIncentiveFractions(0, 0, 0, 0, 0, 100);
// Changing staking parameters
await tokenomics.changeStakingParams(50, 10);
// Changing staking parameters (max staking amount, min weight (in 10_000 form, so 100 is 1%))
await tokenomics.changeStakingParams(ethers.utils.parseEther("300000"), 100);

// Checkpoint to apply changes
await helpers.time.increase(epochLen);
Expand All @@ -208,26 +230,164 @@ describe("StakingIncentives", async () => {

// Lock veOLAS
await olas.approve(ve.address, initialMint);
await ve.createLock(ethers.utils.parseEther("1"), oneYear);
await ve.createLock(ethers.utils.parseEther("100000"), oneYear * 4);
// Transfer OLAS to another account
await olas.transfer(signers[1].address, ethers.utils.parseEther("200000"));
await olas.connect(signers[1]).approve(ve.address, initialMint);
await ve.connect(signers[1]).createLock(ethers.utils.parseEther("200000"), oneYear * 4);

// Add a staking instances as nominees
for (let i = 0; i < numInstances; i++) {
await vw.addNomineeEVM(stakingInstances[i].address, chainId);
}

const chainIds = new Array(numInstances).fill(chainId);
// Vote for the nominees by the deployer
const weights = [maxWeight / 2, maxWeight / 2, 0];
await vw.voteForNomineeWeightsBatch(stakingInstanceAddresses32, chainIds, weights);
// Vote for the nominees by the deployer
const weights2 = [Math.floor(maxWeight / 3), Math.floor(maxWeight / 3), Math.floor(maxWeight / 3) + 1];
await vw.connect(signers[1]).voteForNomineeWeightsBatch(stakingInstanceAddresses32, chainIds, weights2);

// Checkpoint to apply changes
await helpers.time.increase(epochLen);
await tokenomics.checkpoint();

// Add a staking instance as a nominee
await vw.addNomineeEVM(stakingInstance.address, chainId);
// Get the staking inflation for the previous epoch
const lastPoint = await tokenomics.epochCounter() - 1;
// Get the staking point of the last epoch
const sp = await tokenomics.mapEpochStakingPoints(lastPoint);

// Calculate total staking incentive and return amounts
let totalStakingIncentive = ethers.BigNumber.from(0);
let totalReturnAmount = ethers.BigNumber.from(0);
for (let i = 0; i < numInstances; i++) {
const res = await dispenser.callStatic.calculateStakingIncentives(numClaimedEpochs, chainId,
stakingInstanceAddresses32[i], 18);
totalStakingIncentive = totalStakingIncentive.add(res.totalStakingIncentive);
totalReturnAmount = totalReturnAmount.add(res.totalReturnAmount);
}

// Get the difference: staking inflation - (staking incentive + staking return)
let diff = sp.stakingIncentive.sub(totalStakingIncentive.add(totalReturnAmount));
expect(diff).to.lt(bnDelta);

// Sort staking addresses
stakingInstanceAddresses32.sort(compare);
// Claim staking incentives
await dispenser.claimStakingIncentivesBatch(numClaimedEpochs, [chainId], [stakingInstanceAddresses32],
[bridgePayload], [0]);

// Check that the target contract got OLAS
let sumBalance = ethers.BigNumber.from(0);
let balances = new Array(numInstances).fill(0);
for (let i = 0; i < numInstances; i++) {
balances[i] = await olas.balanceOf(stakingInstances[i].address);
sumBalance = sumBalance.add(balances[i]);
expect(balances[i]).to.gt(0);
}

// Since the veOLAS power is bigger than staking inflation, all the staking inflation must be used
diff = sp.stakingIncentive.sub(sumBalance);
expect(diff).to.lt(bnDelta);

// Restore to the state of the snapshot
await snapshot.restore();
});

// Vote for the nominee
const stakingTarget = convertAddressToBytes32(stakingInstance.address);
await vw.voteForNomineeWeights(stakingTarget, chainId, defaultWeight);
it("Claim staking incentives with total veOLAS power smaller than inflation", async () => {
// Take a snapshot of the current state of the blockchain
const snapshot = await helpers.takeSnapshot();

// Set staking fraction to 100%
await tokenomics.changeIncentiveFractions(0, 0, 0, 0, 0, 100);
// Changing staking parameters (max staking amount, min weight (in 10_000 form, so 100 is 1%))
// The limit is much smaller than the staking inflation
await tokenomics.changeStakingParams(ethers.utils.parseEther("3"), 100);

// Checkpoint to apply changes
await helpers.time.increase(epochLen);
await tokenomics.checkpoint();

// Unpause the dispenser
await dispenser.setPauseState(0);

// Lock veOLAS
await olas.approve(ve.address, initialMint);
await ve.createLock(ethers.utils.parseEther("1"), oneYear);
// Transfer OLAS to another account
await olas.transfer(signers[1].address, ethers.utils.parseEther("2"));
await olas.connect(signers[1]).approve(ve.address, initialMint);
await ve.connect(signers[1]).createLock(ethers.utils.parseEther("2"), oneYear);

// Add a staking instances as nominees
for (let i = 0; i < numInstances; i++) {
await vw.addNomineeEVM(stakingInstances[i].address, chainId);
}

const chainIds = new Array(numInstances).fill(chainId);
// Vote for the nominees by the deployer
const weights = [maxWeight / 2, maxWeight / 2, 0];
await vw.voteForNomineeWeightsBatch(stakingInstanceAddresses32, chainIds, weights);
// Vote for the nominees by the deployer
const weights2 = [Math.floor(maxWeight / 3), Math.floor(maxWeight / 3), Math.floor(maxWeight / 3) + 1];
await vw.connect(signers[1]).voteForNomineeWeightsBatch(stakingInstanceAddresses32, chainIds, weights2);

// Checkpoint to apply changes
await helpers.time.increase(epochLen);
await tokenomics.checkpoint();

// Get the staking inflation for the previous epoch
const lastPoint = await tokenomics.epochCounter() - 1;
// Get the staking point of the last epoch
const sp = await tokenomics.mapEpochStakingPoints(lastPoint);

// Calculate total staking incentive and return amounts
let totalStakingIncentive = ethers.BigNumber.from(0);
let totalReturnAmount = ethers.BigNumber.from(0);
for (let i = 0; i < numInstances; i++) {
const res = await dispenser.callStatic.calculateStakingIncentives(numClaimedEpochs, chainId,
stakingInstanceAddresses32[i], 18);
totalStakingIncentive = totalStakingIncentive.add(res.totalStakingIncentive);
totalReturnAmount = totalReturnAmount.add(res.totalReturnAmount);
}

// Get the difference: staking inflation - (staking incentive + staking return)
const diff = sp.stakingIncentive.sub(totalStakingIncentive.add(totalReturnAmount));
expect(diff).to.lt(bnDelta);

// Sort staking addresses
stakingInstanceAddresses32.sort(compare);
// Claim staking incentives
await dispenser.claimStakingIncentives(numClaimedEpochs, chainId, stakingTarget, bridgePayload);
await dispenser.claimStakingIncentivesBatch(numClaimedEpochs, [chainId], [stakingInstanceAddresses32],
[bridgePayload], [0]);

// Check that the target contract got OLAS
let olasBalance = await olas.balanceOf(stakingInstance.address);
expect(olasBalance).to.gt(0);
console.log(olasBalance);
let balances = new Array(numInstances).fill(0);
let totalWeights = new Array(numInstances).fill(0);
let ratios = new Array(numInstances).fill(0);
let ratioSum = 0;
for (let i = 0; i < numInstances; i++) {
const weightBN = ethers.BigNumber.from(weights[i]);
const weightBN2 = ethers.BigNumber.from(weights2[i]);
const maxWeightBN = ethers.BigNumber.from(maxWeight);

totalWeights[i] = ((await ve.getVotes(deployer.address)).mul(weightBN).
add((await ve.getVotes(signers[1].address)).mul(weightBN2))).div(maxWeightBN);

balances[i] = await olas.balanceOf(stakingInstances[i].address);
expect(balances[i]).to.gt(0);

ratios[i] = Number(totalWeights[i]) / Number(balances[i]);
ratioSum += ratios[i];
}

// The weight / balance ratio must be approximately the same for all the elements
const ratioAverage = ratioSum / numInstances;
for (let i = 0; i < numInstances; i++) {
const diff = Math.abs(ratios[i] - ratioAverage);
expect(diff).to.lt(dDelta);
}

// Restore to the state of the snapshot
await snapshot.restore();
Expand Down

0 comments on commit 9d13d87

Please sign in to comment.