From 26702c7ade7423c6e0d93d7a0c0b72d927143ab9 Mon Sep 17 00:00:00 2001 From: Soham Kulkarni <150679961+sohamsk13@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:07:42 +0530 Subject: [PATCH] Add High-Volume Delegation Stress Tests and Gas Usage Benchmarking to Votes.test.js This PR enhances the Votes.test.js suite by introducing stress tests to evaluate gas cost and performance under heavy delegation loads. It addresses the lack of coverage for scenarios involving large numbers of delegation operations in short timeframes, which can be critical in high-traffic voting environments. Key changes: Functional Fixes: Removed ethers.Typed.address() usage and replaced with .address. Used .connect() for correct signer delegation calls. New Stress Test Scenarios: Mass Single-Delegate Delegation: 100+ accounts delegate to a single address, logging total gas usage. Cross-Delegation: Each account delegates to the next, measuring total gas usage. Same-Block Delegation: Multiple delegations batched into one block by disabling automining. Impact: Enables performance profiling for large-scale delegation events. Helps identify potential gas optimizations in the delegation logic. Provides baseline metrics for comparing improvements in future contract iterations. --- test/governance/utils/Votes.test.js | 99 +++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js index 7acacfc6661..3f83e0d7e2a 100644 --- a/test/governance/utils/Votes.test.js +++ b/test/governance/utils/Votes.test.js @@ -1,4 +1,4 @@ -const { ethers } = require('hardhat'); +const { ethers, network } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); @@ -14,6 +14,8 @@ const MODES = { }; const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n]; +const MASS_DELEGATION_ACCOUNTS = 120; +const MASS_AMOUNT = ethers.parseEther('1'); describe('Votes', function () { for (const [mode, artifact] of Object.entries(MODES)) { @@ -62,34 +64,37 @@ describe('Votes', function () { }); it('delegates', async function () { - expect(await this.votes.getVotes(this.accounts[0])).to.equal(0n); - expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); - expect(await this.votes.delegates(this.accounts[0])).to.equal(ethers.ZeroAddress); - expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + expect(await this.votes.getVotes(this.accounts[0].address)).to.equal(0n); + expect(await this.votes.getVotes(this.accounts[1].address)).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0].address)).to.equal(ethers.ZeroAddress); + expect(await this.votes.delegates(this.accounts[1].address)).to.equal(ethers.ZeroAddress); - await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[0])); + await this.votes.connect(this.accounts[0]).delegate(this.accounts[0].address); - expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[0].address]); - expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); - expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); - expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + expect(await this.votes.getVotes(this.accounts[0].address)) + .to.equal(this.amounts[this.accounts[0].address]); + expect(await this.votes.getVotes(this.accounts[1].address)).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0].address)).to.equal(this.accounts[0].address); + expect(await this.votes.delegates(this.accounts[1].address)).to.equal(ethers.ZeroAddress); - await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + await this.votes.connect(this.accounts[1]).delegate(this.accounts[0].address); - expect(await this.votes.getVotes(this.accounts[0])).to.equal( + expect(await this.votes.getVotes(this.accounts[0].address)).to.equal( this.amounts[this.accounts[0].address] + this.amounts[this.accounts[1].address], ); - expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); - expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); - expect(await this.votes.delegates(this.accounts[1])).to.equal(this.accounts[0]); + expect(await this.votes.getVotes(this.accounts[1].address)).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0].address)).to.equal(this.accounts[0].address); + expect(await this.votes.delegates(this.accounts[1].address)).to.equal(this.accounts[0].address); }); it('cross delegates', async function () { - await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); - await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + await this.votes.connect(this.accounts[0]).delegate(this.accounts[1].address); + await this.votes.connect(this.accounts[1]).delegate(this.accounts[0].address); - expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[1].address]); - expect(await this.votes.getVotes(this.accounts[1])).to.equal(this.amounts[this.accounts[0].address]); + expect(await this.votes.getVotes(this.accounts[0].address)) + .to.equal(this.amounts[this.accounts[1].address]); + expect(await this.votes.getVotes(this.accounts[1].address)) + .to.equal(this.amounts[this.accounts[0].address]); }); it('returns total amount of votes', async function () { @@ -97,6 +102,62 @@ describe('Votes', function () { expect(await this.votes.getTotalSupply()).to.equal(totalSupply); }); }); + + // + // 🆕 Gas Optimization Stress Tests + // + describe('gas optimization under mass delegation', function () { + beforeEach(async function () { + this.accounts = await ethers.getSigners(); + this.votes = await ethers.deployContract(artifact, ['Mass Vote', '1']); + // Mint tokens to many accounts + for (let i = 0; i < MASS_DELEGATION_ACCOUNTS; i++) { + await this.votes.$_mint(this.accounts[i].address, MASS_AMOUNT); + } + }); + + it('measures gas for 100+ accounts delegating to a single delegate', async function () { + const delegateAddr = this.accounts[0].address; + let totalGas = 0n; + + for (let i = 1; i < MASS_DELEGATION_ACCOUNTS; i++) { + const tx = await this.votes.connect(this.accounts[i]).delegate(delegateAddr); + const receipt = await tx.wait(); + totalGas += receipt.gasUsed; + } + + console.log(`Total gas for ${MASS_DELEGATION_ACCOUNTS - 1} delegations: ${totalGas}`); + }); + + it('measures gas for cross-delegation', async function () { + let totalGas = 0n; + + for (let i = 0; i < MASS_DELEGATION_ACCOUNTS; i++) { + const targetAddr = this.accounts[(i + 1) % MASS_DELEGATION_ACCOUNTS].address; + const tx = await this.votes.connect(this.accounts[i]).delegate(targetAddr); + const receipt = await tx.wait(); + totalGas += receipt.gasUsed; + } + + console.log(`Total gas for cross-delegation of ${MASS_DELEGATION_ACCOUNTS} accounts: ${totalGas}`); + }); + + it('handles multiple delegations in the same block', async function () { + await network.provider.send('evm_setAutomine', [false]); + + for (let i = 1; i <= 50; i++) { + await this.votes + .connect(this.accounts[i]) + .delegate(this.accounts[0].address); + } + + await network.provider.send('evm_mine'); + await network.provider.send('evm_setAutomine', [true]); + + expect(await this.votes.getVotes(this.accounts[0].address)) + .to.equal(MASS_AMOUNT * 51n); // self + 50 delegators + }); + }); }); } });