diff --git a/.solcover.js b/.solcover.js index 4e7bed3..8e55a91 100644 --- a/.solcover.js +++ b/.solcover.js @@ -9,10 +9,13 @@ module.exports = { "bridges/test/WormholeL1Receiver.sol", "bridges/test/WormholeL1Sender.sol", "bridges/test/WormholeL2ReceiverL1Sender.sol", + "multisigs/test/DelegatecallExploit.sol", + "multisigs/test/MockTimelockCM.sol", + "multisigs/test/MockTreasury.sol", "test/BridgeSetup.sol", "test/BrokenERC20.sol", + "test/EchidnaVoteWeightingAssert.sol", "test/SafeSetup.sol", - "multisigs/test/MockTimelockCM.sol", - "multisigs/test/MockTreasury.sol", + "test/VoteWeightingFuzzing.sol" ] -}; +}; \ No newline at end of file diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index d116ff1..a5e488c 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -159,6 +159,8 @@ contract VoteWeighting { // Set of Nominee structs Nominee[] public setNominees; + // Set of removed Nominee structs + Nominee[] public setRemovedNominees; // Mapping of hash(Nominee struct) => nominee Id mapping(bytes32 => uint256) public mapNomineeIds; // Mapping of hash(Nominee struct) => previously removed nominee flag @@ -203,7 +205,10 @@ contract VoteWeighting { owner = msg.sender; ve = _ve; timeSum = block.timestamp / WEEK * WEEK; - setNominees.push(Nominee(bytes32(0), 0)); + // Push empty element to the zero-th index + setNominees.push(Nominee(0, 0)); + // For symmetry, push empty element to the zero-th index in the removed Nominee set as well + setRemovedNominees.push(Nominee(0, 0)); } /// @dev Fill sum of nominee weights for the same type week-over-week for missed checkins and return the sum for the future week. @@ -381,14 +386,14 @@ contract VoteWeighting { emit DispenserUpdated(newDispenser); } - /// @dev Checkpoint to fill data common for all nominees. + /// @dev Checkpoints to fill data common for all nominees. function checkpoint() external { uint256 totalSum = _getSum(); emit Checkpoint(msg.sender, totalSum); } - /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees. + /// @dev Checkpoints to fill data for both a specific nominee and common for all nominees. /// @param account Address of the nominee. /// @param chainId Chain Id. function checkpointNominee(bytes32 account, uint256 chainId) external { @@ -398,11 +403,10 @@ contract VoteWeighting { emit CheckpointNominee(msg.sender, account, chainId, nomineeWeight, totalSum); } - /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18) and a sum of weights. - /// Inflation which will be received by it is inflation_rate * relativeWeight / 1e18. + /// @dev Gets Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18) and a sum of weights. /// @param account Address of the nominee in byte32 standard. /// @param chainId Chain Id. - /// @param time Relative weight at the specified timestamp in the past or present. + /// @param time Timestamp in the past or present. /// @return weight Value of relative weight normalized to 1e18. /// @return totalSum Sum of nominee weights. function _nomineeRelativeWeight( @@ -422,9 +426,7 @@ contract VoteWeighting { } } - /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 and the sum of weights. - /// (e.g. 1.0 == 1e18). Inflation which will be received by it is - /// inflation_rate * relativeWeight / 1e18. + /// @dev Gets Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18) and a sum of weights. /// @param account Address of the nominee in bytes32 form. /// @param chainId Chain Id. /// @param time Relative weight at the specified timestamp in the past or present. @@ -438,9 +440,8 @@ contract VoteWeighting { (relativeWeight, totalSum) = _nomineeRelativeWeight(account, chainId, time); } - /// @dev Get nominee weight normalized to 1e18 and also fill all the unfilled values for type and nominee records. - /// Also, get the total sum of all the nominee weights. - /// @notice Any address can call, however nothing is recorded if the values are filled already. + /// @dev Gets nominee weight normalized to 1e18 and also gets the total sum of all the nominee weights. + /// @notice Nothing is recorded if the values are already filled. /// @param account Address of the nominee in bytes32 form. /// @param chainId Chain Id. /// @param time Relative weight at the specified timestamp in the past or present. @@ -458,7 +459,7 @@ contract VoteWeighting { emit NomineeRelativeWeightWrite(msg.sender, account, chainId, nomineeWeight, totalSum, relativeWeight); } - /// @dev Allocate voting power for changing pool weights. + /// @dev Allocates voting power for changing pool weights. /// @param account Address of the nominee the `msg.sender` votes for in bytes32 form. /// @param chainId Chain Id. /// @param weight Weight for a nominee in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0. @@ -548,7 +549,7 @@ contract VoteWeighting { emit VoteForNominee(msg.sender, account, chainId, weight); } - /// @dev Allocate voting power for changing pool weights in batch. + /// @dev Allocates voting power for changing pool weights in a batch set. /// @param accounts Set of nominee addresses in bytes32 form the `msg.sender` votes for. /// @param chainIds Set of corresponding chain Ids. /// @param weights Weights for a nominees in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0. @@ -603,8 +604,9 @@ contract VoteWeighting { pointsSum[nextTime].bias = newSum; timeSum = nextTime; - // Add to the removed nominee map + // Add to the removed nominee map and set mapRemovedNominees[nomineeHash] = true; + setRemovedNominees.push(nominee); // Remove nominee from the map mapNomineeIds[nomineeHash] = 0; @@ -683,30 +685,44 @@ contract VoteWeighting { return setNominees.length - 1; } + /// @dev Get the total number of removed nominees. + /// @notice The zero-th default nominee Id with id == 0 does not count. + /// @return Total number of removed nominees. + function getNumRemovedNominees() external view returns (uint256) { + return setRemovedNominees.length - 1; + } + /// @dev Gets a full set of nominees. /// @notice The returned set includes the zero-th empty nominee instance. - /// @return nominees Set of all the nominees in the contract. - function getAllNominees() external view returns (Nominee[] memory nominees) { - nominees = setNominees; + /// @return Set of all the nominees in the contract. + function getAllNominees() external view returns (Nominee[] memory) { + return setNominees; + } + + /// @dev Gets a full set of removed nominees. + /// @notice The returned set includes the zero-th empty nominee instance. + /// @return Set of all the removed nominees in the contract. + function getAllRemovedNominees() external view returns (Nominee[] memory) { + return setRemovedNominees; } /// @dev Gets the nominee Id in the global nominees set. /// @param account Nominee address in bytes32 form. /// @param chainId Chain Id. - /// @return id Nominee Id in the global set of Nominee struct values. - function getNomineeId(bytes32 account, uint256 chainId) external view returns (uint256 id) { + /// @return Nominee Id in the global set of Nominee struct values. + function getNomineeId(bytes32 account, uint256 chainId) external view returns (uint256) { // Get the nominee struct and hash Nominee memory nominee = Nominee(account, chainId); bytes32 nomineeHash = keccak256(abi.encode(nominee)); - id = mapNomineeIds[nomineeHash]; + return mapNomineeIds[nomineeHash]; } /// @dev Get the nominee address and its corresponding chain Id. /// @notice The zero-th default nominee Id with id == 0 does not count. /// @param id Nominee Id in the global set of Nominee struct values. - /// @return nominee Nominee address in bytes32 form and chain Id. - function getNominee(uint256 id) external view returns (Nominee memory nominee) { + /// @return Nominee address in bytes32 form and chain Id. + function getNominee(uint256 id) external view returns (Nominee memory) { // Get the total number of nominees in the contract uint256 totalNumNominees = setNominees.length - 1; // Check for the zero id or the overflow @@ -716,7 +732,7 @@ contract VoteWeighting { revert Overflow(id, totalNumNominees); } - nominee = setNominees[id]; + return setNominees[id]; } /// @dev Get the set of nominee addresses and corresponding chain Ids. @@ -724,11 +740,7 @@ contract VoteWeighting { /// @param startId Start Id of the nominee in the global set of Nominee struct values. /// @param numNominees Number of nominees to get. /// @return nominees Set of nominee accounts in bytes32 form and chain Ids. - function getNominees( - uint256 startId, - uint256 numNominees - ) external view returns (Nominee[] memory nominees) - { + function getNominees(uint256 startId, uint256 numNominees) external view returns (Nominee[] memory nominees) { // Check for the zero id or the overflow if (startId == 0 || numNominees == 0) { revert ZeroValue(); diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index 8614764..3bde3a4 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -4,7 +4,7 @@ const { expect } = require("chai"); const { ethers } = require("hardhat"); const helpers = require("@nomicfoundation/hardhat-network-helpers"); -describe("Voting Escrow OLAS", function () { +describe("Vote Weighting veOLAS", function () { let olas; let ve; let vw; @@ -609,6 +609,14 @@ describe("Voting Escrow OLAS", function () { // Remove the nominee await vw.removeNominee(nominees[0], chainId); + // Get the set of removed nominees + const numRemovedNominees = await vw.getNumRemovedNominees(); + expect(numRemovedNominees).to.equal(1); + const setRemovedNominees = await vw.getAllRemovedNominees(); + // The set itself has one more zero-th empty element + expect(setRemovedNominees.length).to.equal(2); + expect(numRemovedNominees).to.equal(setRemovedNominees.length - 1); + // Get the removed nominee Id id = await vw.getNomineeId(nominees[0], chainId); expect(id).to.equal(0);