diff --git a/audits/README.md b/audits/README.md index ed2ea9b..64d4995 100644 --- a/audits/README.md +++ b/audits/README.md @@ -25,6 +25,8 @@ An internal audit with a focus on `OptimismMesseger and WormholeMessenger` is lo An internal audit with a focus on `Guard for Community Multisig (CM) (modular version)` is located in this folder: [internal audit 10](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal10). +An internal audit with a focus on `VoteWeighting` is located in this folder: [internal audit 10](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal12). + ### External audit Following the initial contracts [audit report](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/Valory%20Review%20Final.pdf), the recommendations are addressed here: [feedback](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/Addressing%20Initial%20ApeWorX%20Recommentations.pdf). @@ -37,4 +39,4 @@ The final audit reports: - [v3](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/Valory%20Smart%20Contract%20Audit%20by%20Solidity%20Finance-v1.1.0.pdf), -- [v4](https://sourcehat.com/audits/ValoryOLAS/). \ No newline at end of file +- [v4](https://sourcehat.com/audits/ValoryOLAS/). diff --git a/audits/internal12/README.md b/audits/internal12/README.md new file mode 100644 index 0000000..624d4eb --- /dev/null +++ b/audits/internal12/README.md @@ -0,0 +1,151 @@ +# autonolas-governance-audit +The review has been performed based on the contract code in the following repository:
+`https://github.com/valory-xyz/autonolas-governance`
+commit: `6c39aa6dcdc0111fd8d70bb4df3433d93d4cae99` or `tag: v1.2.0-internal-audit`
+ +Update: 13-05-2024
+ +## Objectives +The audit focused on VoteWeighting.
+ +### Flatten version +Flatten version of contracts. [contracts](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal12/analysis/contracts) + + +### Coverage +Hardhat coverage has been performed before the audit and can be found here: +```sh +--------------------------------------|----------|----------|----------|----------|----------------| +File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | +--------------------------------------|----------|----------|----------|----------|----------------| + VoteWeighting.sol | 100 | 100 | 100 | 100 | | +``` + +### Fuzzing VoteWeighting + +#### Prepare contracts for fuzzing +contracts/test/VoteWeightingFuzzing.sol
+contracts/test/EchidnaVoteWeightingAssert.sol
+ +#### Fuzzing +```sh +# Move the script to the root of the project +cp start_echidna.sh ../../../../../../ +# Move config file to the root of the project +cp echidna_assert.yaml ../../../../../ +cd ../../../../../../ +# Run +./start_echidna.sh +``` +result overflow: [fuzzing-overflow.PNG](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal12/analysis/fuzzing/overflow/fuzzing-overflow.PNG)
+result assert: [fuzzing-assert.PNG](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal12/analysis/fuzzing/overflow/fuzzing-assert.PNG) + + +### Security issues +Details in [slither_full](https://github.com/valory-xyz/autonolas-governance/blob/main/audits/internal12/analysis/slither_full.txt)
+ +#### Issue +Bug in viper->solidity conversion. +```sh +convert in viper more safe than solidity +https://vyper.readthedocs.io/_/downloads/en/stable/pdf/ +• Converting between signed and unsigned integers reverts if the input is negative. +bug on line: +uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); + +Proof: +uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); +to +// Hack +pp = IVEOLAS(ve).getLastUserPoint(msg.sender).slope; +pp = -10; +uint256 slope = uint256(uint128(pp)); +console.log(slope); +console.log("bug: negative getLastUserPoint() is possible"); + +340282366920938463463374607431768211446 +bug: negative getLastUserPoint() is ok +``` +#### Minor issue +CEI pattern:
+```sh +Not CEI pattern. Move to end. + // Remove nominee in dispenser, if applicable + address localDispenser = dispenser; + if (localDispenser != address(0)) { + IDispenser(localDispenser).removeNominee(nomineeHash); + } + +``` +Lacks a zero-check on:
+```sh +function changeDispenser(address newDispenser) external {} +``` +No events:
+```sh +function changeDispenser(address newDispenser) external {} +function checkpoint() ? +function checkpointNominee() ? +function nomineeRelativeWeightWrite() ? +``` +Naming test issue:
+```sh +Rename test\VoteWeighting.js +describe("Voting Escrow OLAS", function () { +``` +README issue:
+```sh +No link to https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/GaugeController.vy +``` +Pay attention:
+``` +https://github.com/trailofbits/publications/blob/master/reviews/CurveDAO.pdf -> 18. Several loops are not executable due to gaslimitation +Discussion: I don't think this is a problem for our version. +``` +Version solidity:
+```sh +For contracts that are planned to be deployed in mainnet, it is necessary to use the features of the latest hard fork. +https://soliditylang.org/blog/2024/03/14/solidity-0.8.25-release-announcement/ +``` + +#### Notes +Notes for UX/UI: +```sh + // Remove the nominee + await vw.removeNominee(nominees[0], chainId); + // Get the removed nominee Id + id = await vw.getNomineeId(nominees[0], chainId); + expect(id).to.equal(0); + // Get the id for the second nominee that was shifted from 2 to 1 + id = await vw.getNomineeId(nominees[1], chainId); + + + function getNomineeId(bytes32 account, uint256 chainId) external view returns (uint256 id) { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + id = mapNomineeIds[nomineeHash]; + } + function getNominee(uint256 id) external view returns (Nominee memory nominee) { + // Get the total number of nominees in the contract + uint256 totalNumNominees = setNominees.length - 1; + // Check for the zero id or the overflow + if (id == 0) { + revert ZeroValue(); + } else if (id > totalNumNominees) { + revert Overflow(id, totalNumNominees); + } + + nominee = setNominees[id]; + } +Due to operation removeNominee(), you must keep in mind that for the same `id` there can be DIFFERENT(!) `nominee` in different time. ref: tests +Does the developer need to add clarification in comments to the source code? +``` +General notes (from Curve Finance audit):
+```sh +https://github.com/trailofbits/publications/blob/master/reviews/CurveDAO.pdf +4. GaugeController allowsfor quick vote andwithdrawvoting strategy: ref: source variable WEIGHT_VOTE_DELAY +18. Several loops are not executable due to gaslimitation +``` + + diff --git a/audits/internal12/analysis/contracts/VoteWeighting-flatten.sol b/audits/internal12/analysis/contracts/VoteWeighting-flatten.sol new file mode 100644 index 0000000..f3c5757 --- /dev/null +++ b/audits/internal12/analysis/contracts/VoteWeighting-flatten.sol @@ -0,0 +1,770 @@ +// Sources flattened with hardhat v2.22.3 https://hardhat.org + +// SPDX-License-Identifier: MIT + +// File contracts/VoteWeighting.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity ^0.8.23; + +// Dispenser interface +interface IDispenser { + /// @dev Records nominee addition in dispenser. + /// @param nomineeHash Nominee hash. + function addNominee(bytes32 nomineeHash) external; + + /// @dev Records nominee removal. + /// @param nomineeHash Nominee hash. + function removeNominee(bytes32 nomineeHash) external; +} + +// veOLAS interface +interface IVEOLAS { + // Structure for voting escrow points + // The struct size is two storage slots of 2 * uint256 (128 + 128 + 64 + 64 + 128) + struct PointVoting { + // w(i) = at + b (bias) + int128 bias; + // dw / dt = a (slope) + int128 slope; + // Timestamp. It will never practically be bigger than 2^64 - 1 + uint64 ts; + // Block number. It will not be bigger than the timestamp + uint64 blockNumber; + // Token amount. It will never practically be bigger. Initial OLAS cap is 1 bn tokens, or 1e27. + // After 10 years, the inflation rate is 2% per year. It would take 1340+ years to reach 2^128 - 1 + uint128 balance; + } + + /// @dev Gets the `account`'s lock end time. + /// @param account Account address. + /// @return unlockTime Lock end time. + function lockedEnd(address account) external view returns (uint256 unlockTime); + + /// @dev Gets the most recently recorded user point for `account`. + /// @param account Account address. + /// @return pv Last checkpoint. + function getLastUserPoint(address account) external view returns (PointVoting memory pv); +} + +/// @dev Only `owner` has a privilege, but the `sender` was provided. +/// @param sender Sender address. +/// @param owner Required sender address as an owner. +error OwnerOnly(address sender, address owner); + +/// @dev Provided zero address. +error ZeroAddress(); + +/// @dev Zero value when it has to be different from zero. +error ZeroValue(); + +/// @dev Wrong length of two arrays. +/// @param numValues1 Number of values in a first array. +/// @param numValues2 Number of values in a second array. +error WrongArrayLength(uint256 numValues1, uint256 numValues2); + +/// @dev Value overflow. +/// @param provided Overflow value. +/// @param max Maximum possible value. +error Overflow(uint256 provided, uint256 max); + +/// @dev Underflow value. +/// @param provided Provided value. +/// @param expected Minimum expected value. +error Underflow(uint256 provided, uint256 expected); + +/// @dev Nominee does not exist. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeDoesNotExist(bytes32 account, uint256 chainId); + +/// @dev Nominee already exists. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeAlreadyExists(bytes32 account, uint256 chainId); + +/// @dev Value lock is expired. +/// @param account Address that is checked for the locked value. +/// @param deadline The lock expiration deadline. +/// @param curTime Current timestamp. +error LockExpired(address account, uint256 deadline, uint256 curTime); + +/// @dev The vote has been performed already. +/// @param voter Voter address. +/// @param curTime Current time. +/// @param nextAllowedVotingTime Next allowed voting time. +error VoteTooOften(address voter, uint256 curTime, uint256 nextAllowedVotingTime); + +/// @dev Nominee is not in the removed nominee map. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeNotRemoved(bytes32 account, uint256 chainId); + +/// @dev Nominee is in the removed nominee map. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeRemoved(bytes32 account, uint256 chainId); + +// Point struct +struct Point { + uint256 bias; + uint256 slope; +} + +// Voted slope struct +struct VotedSlope { + uint256 slope; + uint256 power; + uint256 end; +} + +// Nominee struct +struct Nominee { + bytes32 account; + uint256 chainId; +} + + +/// @title VoteWeighting - Smart contract for Vote Weighting with specific nominees composed of address and chain Id +/// @author Aleksandr Kuperman - +/// @author Andrey Lebedev - +/// @author Mariapia Moscatiello - +contract VoteWeighting { + event OwnerUpdated(address indexed owner); + event VoteForNominee(address indexed user, bytes32 indexed nominee, uint256 chainId, uint256 weight); + event AddNominee(bytes32 indexed account, uint256 chainId, uint256 id); + event RemoveNominee(bytes32 indexed account, uint256 chainId, uint256 newSum); + + // 7 * 86400 seconds - all future times are rounded by week + uint256 public constant WEEK = 604_800; + // Cannot change weight votes more often than once in 10 days + uint256 public constant WEIGHT_VOTE_DELAY = 864_000; + // Max weight amount + uint256 public constant MAX_WEIGHT = 10_000; + // Maximum chain Id as per EVM specs + uint256 public constant MAX_EVM_CHAIN_ID = type(uint64).max / 2 - 36; + // veOLAS contract address + address public immutable ve; + // Contract owner address + address public owner; + // Dispenser contract + address public dispenser; + + // Set of Nominee structs + Nominee[] public setNominees; + // Mapping of hash(Nominee struct) => nominee Id + mapping(bytes32 => uint256) public mapNomineeIds; + // Mapping of hash(Nominee struct) => previously removed nominee flag + mapping(bytes32 => bool) public mapRemovedNominees; + + // user -> hash(Nominee struct) -> VotedSlope + mapping(address => mapping(bytes32 => VotedSlope)) public voteUserSlopes; + // Total vote power used by user + mapping(address => uint256) public voteUserPower; + // Last user vote's timestamp for each hash(Nominee struct) + mapping(address => mapping(bytes32 => uint256)) public lastUserVote; + + // Past and scheduled points for nominee weight, sum of weights per type, total weight + // Point is for bias+slope + // changes_* are for changes in slope + // time_* are for the last change timestamp + // timestamps are rounded to whole weeks + + // hash(Nominee struct) -> time -> Point + mapping(bytes32 => mapping(uint256 => Point)) public pointsWeight; + // hash(Nominee struct) -> time -> slope + mapping(bytes32 => mapping(uint256 => uint256)) public changesWeight; + // hash(Nominee struct) -> last scheduled time (next week) + mapping(bytes32 => uint256) public timeWeight; + + // time -> Point + mapping(uint256 => Point) public pointsSum; + // time -> slope + mapping(uint256 => uint256) public changesSum; + // last scheduled time (next week) + uint256 public timeSum; + + /// @dev Contract constructor. + /// @param _ve Voting Escrow contract address. + constructor(address _ve) { + // Check for the zero address + if (_ve == address(0)) { + revert ZeroAddress(); + } + + // Set initial parameters + owner = msg.sender; + ve = _ve; + timeSum = block.timestamp / WEEK * WEEK; + setNominees.push(Nominee(bytes32(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. + /// @return Sum of weights. + function _getSum() internal returns (uint256) { + // t is always > 0 as it is set in the constructor + uint256 t = timeSum; + Point memory pt = pointsSum[t]; + for (uint256 i = 0; i < 500; i++) { + if (t > block.timestamp) { + break; + } + t += WEEK; + uint256 dBias = pt.slope * WEEK; + if (pt.bias > dBias) { + pt.bias -= dBias; + uint256 dSlope = changesSum[t]; + pt.slope -= dSlope; + } else { + pt.bias = 0; + pt.slope = 0; + } + + pointsSum[t] = pt; + if (t > block.timestamp) { + timeSum = t; + } + } + return pt.bias; + } + + /// @dev Fill historic nominee weights week-over-week for missed checkins and return the total for the future week. + /// @param account Nominee account address in bytes32 form. + /// @param chainId Nominee chain Id. + /// @return Nominee weight. + function _getWeight(bytes32 account, uint256 chainId) internal returns (uint256) { + // Construct the nominee struct + Nominee memory nominee = Nominee(account, chainId); + + // Check that the nominee exists or has been removed + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + if (!mapRemovedNominees[nomineeHash] && mapNomineeIds[nomineeHash] == 0) { + revert NomineeDoesNotExist(account, chainId); + } + + // t is always > 0 as it is set during the addNominee() call + uint256 t = timeWeight[nomineeHash]; + Point memory pt = pointsWeight[nomineeHash][t]; + for (uint256 i = 0; i < 500; i++) { + if (t > block.timestamp) { + break; + } + t += WEEK; + uint256 dBias = pt.slope * WEEK; + if (pt.bias > dBias) { + pt.bias -= dBias; + uint256 dSlope = changesWeight[nomineeHash][t]; + pt.slope -= dSlope; + } else { + pt.bias = 0; + pt.slope = 0; + } + + pointsWeight[nomineeHash][t] = pt; + if (t > block.timestamp) { + timeWeight[nomineeHash] = t; + } + } + return pt.bias; + } + + /// @dev Add nominee address along with the chain Id. + /// @param nominee Nominee account address and chainId. + function _addNominee(Nominee memory nominee) internal { + // Check for the nominee existence + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + if (mapNomineeIds[nomineeHash] > 0) { + revert NomineeAlreadyExists(nominee.account, nominee.chainId); + } + + // Check for the previously removed nominee + if (mapRemovedNominees[nomineeHash]) { + revert NomineeRemoved(nominee.account, nominee.chainId); + } + + uint256 id = setNominees.length; + mapNomineeIds[nomineeHash] = id; + // Push the nominee into the list + setNominees.push(nominee); + + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + timeWeight[nomineeHash] = nextTime; + + // Enable nominee in dispenser, if applicable + address localDispenser = dispenser; + if (localDispenser != address(0)) { + IDispenser(localDispenser).addNominee(nomineeHash); + } + + emit AddNominee(nominee.account, nominee.chainId, id); + } + + /// @dev Add EVM nominee address along with the chain Id. + /// @param account Address of the nominee. + /// @param chainId Chain Id. + function addNomineeEVM(address account, uint256 chainId) external { + // Check for the zero address + if (account == address(0)) { + revert ZeroAddress(); + } + + // Check for zero chain Id + if (chainId == 0) { + revert ZeroValue(); + } + + // Check for the chain Id overflow + if (chainId > MAX_EVM_CHAIN_ID) { + revert Overflow(chainId, MAX_EVM_CHAIN_ID); + } + + Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); + + // Record nominee instance + _addNominee(nominee); + } + + /// @dev Add Non-EVM nominee address along with the chain Id. + /// @param account Address of the nominee in byte32 standard. + /// @param chainId Chain Id. + function addNomineeNonEVM(bytes32 account, uint256 chainId) external { + // Check for the zero address + if (account == bytes32(0)) { + revert ZeroAddress(); + } + + // Check for the chain Id underflow + if (MAX_EVM_CHAIN_ID >= chainId) { + revert Underflow(chainId, MAX_EVM_CHAIN_ID + 1); + } + + Nominee memory nominee = Nominee(account, chainId); + + // Record nominee instance + _addNominee(nominee); + } + + /// @dev Changes the owner address. + /// @param newOwner Address of a new owner. + function changeOwner(address newOwner) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + // Check for the zero address + if (newOwner == address(0)) { + revert ZeroAddress(); + } + + owner = newOwner; + emit OwnerUpdated(newOwner); + } + + /// @dev Changes the dispenser contract address. + /// @notice Dispenser can a zero address if the contract needs to serve a general purpose. + /// @param newDispenser New dispenser contract address. + function changeDispenser(address newDispenser) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + dispenser = newDispenser; + } + + /// @dev Checkpoint to fill data common for all nominees. + function checkpoint() external { + _getSum(); + } + + /// @dev Checkpoint 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 { + _getWeight(account, chainId); + _getSum(); + } + + /// @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. + /// @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. + /// @return weight Value of relative weight normalized to 1e18. + /// @return totalSum Sum of nominee weights. + function _nomineeRelativeWeight( + bytes32 account, + uint256 chainId, + uint256 time + ) internal view returns (uint256 weight, uint256 totalSum) { + uint256 t = time / WEEK * WEEK; + totalSum = pointsSum[t].bias; + + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + if (totalSum > 0) { + uint256 nomineeWeight = pointsWeight[nomineeHash][t].bias; + weight = 1e18 * nomineeWeight / totalSum; + } + } + + /// @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. + /// @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. + /// @return weight Value of relative weight normalized to 1e18. + /// @return totalSum Sum of nominee weights. + function nomineeRelativeWeight( + bytes32 account, + uint256 chainId, + uint256 time + ) external view returns (uint256 weight, uint256 totalSum) { + (weight, 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. + /// @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. + /// @return weight Value of relative weight normalized to 1e18. + /// @return totalSum Sum of nominee weights. + function nomineeRelativeWeightWrite( + bytes32 account, + uint256 chainId, + uint256 time + ) external returns (uint256 weight, uint256 totalSum) { + _getWeight(account, chainId); + _getSum(); + (weight, totalSum) = _nomineeRelativeWeight(account, chainId, time); + } + + /// @dev Allocate 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. + function voteForNomineeWeights(bytes32 account, uint256 chainId, uint256 weight) public { + // Get the nominee hash + bytes32 nomineeHash = keccak256(abi.encode(Nominee(account, chainId))); + + // Check for the previously removed nominee + if (mapRemovedNominees[nomineeHash]) { + revert NomineeRemoved(account, chainId); + } + + uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); + uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + + // Check for the lock end expiration + if (nextTime >= lockEnd) { + revert LockExpired(msg.sender, lockEnd, nextTime); + } + + // Check for the weight number + if (weight > MAX_WEIGHT) { + revert Overflow(weight, MAX_WEIGHT); + } + + // Check for the last voting time + uint256 nextAllowedVotingTime = lastUserVote[msg.sender][nomineeHash] + WEIGHT_VOTE_DELAY; + if (nextAllowedVotingTime > block.timestamp) { + revert VoteTooOften(msg.sender, block.timestamp, nextAllowedVotingTime); + } + + // Prepare old and new slopes and biases + VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; + uint256 oldBias; + if (oldSlope.end > nextTime) { + oldBias = oldSlope.slope * (oldSlope.end - nextTime); + } + + VotedSlope memory newSlope = VotedSlope({ + slope: slope * weight / MAX_WEIGHT, + end: lockEnd, + power: weight + }); + + uint256 newBias = newSlope.slope * (lockEnd - nextTime); + + uint256 powerUsed = voteUserPower[msg.sender]; + powerUsed = powerUsed + newSlope.power - oldSlope.power; + voteUserPower[msg.sender] = powerUsed; + if (powerUsed > MAX_WEIGHT) { + revert Overflow(powerUsed, MAX_WEIGHT); + } + + // Remove old and schedule new slope changes + // Remove slope changes for old slopes + // Schedule recording of initial slope for nextTime + pointsWeight[nomineeHash][nextTime].bias = _maxAndSub(_getWeight(account, chainId) + newBias, oldBias); + pointsSum[nextTime].bias = _maxAndSub(_getSum() + newBias, oldBias); + if (oldSlope.end > nextTime) { + pointsWeight[nomineeHash][nextTime].slope = + _maxAndSub(pointsWeight[nomineeHash][nextTime].slope + newSlope.slope, oldSlope.slope); + pointsSum[nextTime].slope = _maxAndSub(pointsSum[nextTime].slope + newSlope.slope, oldSlope.slope); + } else { + pointsWeight[nomineeHash][nextTime].slope += newSlope.slope; + pointsSum[nextTime].slope += newSlope.slope; + } + if (oldSlope.end > block.timestamp) { + // Cancel old slope changes if they still didn't happen + changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; + changesSum[oldSlope.end] -= oldSlope.slope; + } + // Add slope changes for new slopes + changesWeight[nomineeHash][newSlope.end] += newSlope.slope; + changesSum[newSlope.end] += newSlope.slope; + + voteUserSlopes[msg.sender][nomineeHash] = newSlope; + + // Record last action time + lastUserVote[msg.sender][nomineeHash] = block.timestamp; + + emit VoteForNominee(msg.sender, account, chainId, weight); + } + + /// @dev Allocate voting power for changing pool weights in batch. + /// @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. + function voteForNomineeWeightsBatch( + bytes32[] memory accounts, + uint256[] memory chainIds, + uint256[] memory weights + ) external { + if (accounts.length != chainIds.length || accounts.length != weights.length) { + revert WrongArrayLength(accounts.length, weights.length); + } + + // Traverse all accounts and weights + for (uint256 i = 0; i < accounts.length; ++i) { + voteForNomineeWeights(accounts[i], chainIds[i], weights[i]); + } + } + + function _maxAndSub(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a - b : 0; + } + + /// @dev Removes nominee from the contract and zeros its weight. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + function removeNominee(bytes32 account, uint256 chainId) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(owner, msg.sender); + } + + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Get the nominee id in the nominee set + uint256 id = mapNomineeIds[nomineeHash]; + if (id == 0) { + revert NomineeDoesNotExist(account, chainId); + } + + // Set nominee weight to zero + uint256 oldWeight = _getWeight(account, chainId); + uint256 oldSum = _getSum(); + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + pointsWeight[nomineeHash][nextTime].bias = 0; + timeWeight[nomineeHash] = nextTime; + + // Account for the the sum weight change + uint256 newSum = oldSum - oldWeight; + pointsSum[nextTime].bias = newSum; + timeSum = nextTime; + + // Add to the removed nominee map + mapRemovedNominees[nomineeHash] = true; + + // Remove nominee in dispenser, if applicable + address localDispenser = dispenser; + if (localDispenser != address(0)) { + IDispenser(localDispenser).removeNominee(nomineeHash); + } + + // Remove nominee from the map + mapNomineeIds[nomineeHash] = 0; + // Shuffle the current last nominee id in the set to be placed to the removed one + nominee = setNominees[setNominees.length - 1]; + nomineeHash = keccak256(abi.encode(nominee)); + mapNomineeIds[nomineeHash] = id; + setNominees[id] = nominee; + // Pop the last element from the set + setNominees.pop(); + + emit RemoveNominee(account, chainId, newSum); + } + + /// @dev Retrieves user voting power from a removed nominee. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + function retrieveRemovedNomineeVotingPower(bytes32 account, uint256 chainId) external { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Check that the nominee is removed + if (!mapRemovedNominees[nomineeHash]) { + revert NomineeNotRemoved(account, chainId); + } + + // Get the user old slope + VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; + if (oldSlope.power == 0) { + revert ZeroValue(); + } + + // Cancel old slope changes if they still didn't happen + if (oldSlope.end > block.timestamp) { + changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; + changesSum[oldSlope.end] -= oldSlope.slope; + } + + // Update the voting power + uint256 powerUsed = voteUserPower[msg.sender]; + powerUsed = powerUsed - oldSlope.power; + voteUserPower[msg.sender] = powerUsed; + delete voteUserSlopes[msg.sender][nomineeHash]; + } + + /// @dev Get current nominee weight. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + /// @return Nominee weight. + function getNomineeWeight(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)); + + return pointsWeight[nomineeHash][timeWeight[nomineeHash]].bias; + } + + /// @dev Get sum of nominee weights. + /// @return Sum of nominee weights. + function getWeightsSum() external view returns (uint256) { + return pointsSum[timeSum].bias; + } + + /// @dev Get the total number of nominees. + /// @notice The zero-th default nominee Id with id == 0 does not count. + /// @return Total number of nominees. + function getNumNominees() external view returns (uint256) { + return setNominees.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; + } + + /// @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) { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + id = 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) { + // Get the total number of nominees in the contract + uint256 totalNumNominees = setNominees.length - 1; + // Check for the zero id or the overflow + if (id == 0) { + revert ZeroValue(); + } else if (id > totalNumNominees) { + revert Overflow(id, totalNumNominees); + } + + nominee = setNominees[id]; + } + + /// @dev Get the set of nominee addresses and corresponding chain Ids. + /// @notice The zero-th default nominee Id with id == 0 does not count. + /// @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) + { + // Check for the zero id or the overflow + if (startId == 0 || numNominees == 0) { + revert ZeroValue(); + } + + // Get the last nominee Id requested + uint256 endId = startId + numNominees; + // Get the total number of nominees in the contract with the zero-th nominee + uint256 totalNumNominees = setNominees.length; + + // Check for the overflow + if (endId > totalNumNominees) { + revert Overflow(endId, totalNumNominees); + } + + // Allocate the nominee array + nominees = new Nominee[](numNominees); + + // Traverse selected nominees + for (uint256 i = 0; i < numNominees; ++i) { + uint256 id = i + startId; + // Get the nominee struct + nominees[i] = setNominees[id]; + } + } + + /// @dev Gets next allowed voting time for selected nominees and voters. + /// @notice The function does not check for repeated nominees and voters. + /// @param accounts Set of nominee account addresses. + /// @param chainIds Corresponding set of chain Ids. + /// @param voters Corresponding set of voters for specified nominees. + function getNextAllowedVotingTimes( + bytes32[] memory accounts, + uint256[] memory chainIds, + address[] memory voters + ) external view returns (uint256[] memory nextAllowedVotingTimes) { + // Check array lengths + if (accounts.length != chainIds.length || accounts.length != voters.length) { + revert WrongArrayLength(accounts.length, chainIds.length); + } + + // Allocate the times array + nextAllowedVotingTimes = new uint256[](accounts.length); + + // Traverse nominees and get next available voting times + for (uint256 i = 0; i < accounts.length; ++i) { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(accounts[i], chainIds[i]); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Check for nominee existence + if (mapNomineeIds[nomineeHash] == 0) { + revert NomineeDoesNotExist(accounts[i], chainIds[i]); + } + + // Calculate next allowed voting times + nextAllowedVotingTimes[i] = lastUserVote[voters[i]][nomineeHash] + WEIGHT_VOTE_DELAY; + } + } +} diff --git a/audits/internal12/analysis/contracts/script.sh b/audits/internal12/analysis/contracts/script.sh new file mode 100755 index 0000000..286784b --- /dev/null +++ b/audits/internal12/analysis/contracts/script.sh @@ -0,0 +1,20 @@ +#!/bin/bash + + slither_options=("call-graph" "constructor-calls" "contract-summary" "data-dependency" "function-summary" + "human-summary" "inheritance" "inheritance-graph" "modifiers" "require" "variable-order" "vars-and-auth") + echo -e "\nRunning slither routines ..." + for so in "${slither_options[@]}"; do + echo -e "\t$so" + slither . --print ${so} &> "slither_$so.txt" + done + echo -e "\tfull report" + slither . &> "slither_full.txt" + + # moving generated .dot files to the audit folder + count=`ls -1 *.dot 2>/dev/null | wc -l` + echo -e "\tgenerated $count .dot files" + for _filename in *.dot; do + filename="${_filename%.*}" + cat $_filename | dot -Tpng > slither_$filename.png + done + rm *.dot diff --git a/audits/internal12/analysis/fuzzing/assert/echidna_assert.yaml b/audits/internal12/analysis/fuzzing/assert/echidna_assert.yaml new file mode 100644 index 0000000..30d1d73 --- /dev/null +++ b/audits/internal12/analysis/fuzzing/assert/echidna_assert.yaml @@ -0,0 +1,10 @@ +#testMode: overflow +testMode: assertion +coverage: true +corpusDir: corpusEchidna +coverageFormats: ["html"] +# maxBlockDelay: 12 +# provide solc remappings to crytic-compile +# https://www.justinsilver.com/technology/programming/slither-echidna-remappings/ +cryticArgs: ['--solc-remaps', '@=node_modules/@'] + diff --git a/audits/internal12/analysis/fuzzing/assert/fuzzing-assert.PNG b/audits/internal12/analysis/fuzzing/assert/fuzzing-assert.PNG new file mode 100755 index 0000000..cb1750d Binary files /dev/null and b/audits/internal12/analysis/fuzzing/assert/fuzzing-assert.PNG differ diff --git a/audits/internal12/analysis/fuzzing/assert/start_echidna.sh b/audits/internal12/analysis/fuzzing/assert/start_echidna.sh new file mode 100644 index 0000000..c737e2b --- /dev/null +++ b/audits/internal12/analysis/fuzzing/assert/start_echidna.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -rf corpusEchidna/ +echidna contracts/test/EchidnaVoteWeightingAssert.sol --contract EchidnaVoteWeightingAssert --config echidna_assert.yaml diff --git a/audits/internal12/analysis/fuzzing/overflow/corpusEchidna/covered.1715618908.html b/audits/internal12/analysis/fuzzing/overflow/corpusEchidna/covered.1715618908.html new file mode 100644 index 0000000..022a6dc --- /dev/null +++ b/audits/internal12/analysis/fuzzing/overflow/corpusEchidna/covered.1715618908.html @@ -0,0 +1,2329 @@ +/home/andrey/valory/autonolas-governance/contracts/OLAS.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity ^0.8.15; + 3 | | + 4 | | import "../lib/solmate/src/tokens/ERC20.sol"; + 5 | | + 6 | | /// @dev Only `manager` has a privilege, but the `sender` was provided. + 7 | | /// @param sender Sender address. + 8 | | /// @param manager Required sender address as a manager. + 9 | | error ManagerOnly(address sender, address manager); + 10 | | + 11 | | /// @dev Provided zero address. + 12 | | error ZeroAddress(); + 13 | | + 14 | | /// @title OLAS - Smart contract for the OLAS token. + 15 | | /// @author AL + 16 | | /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz> + 17 | * | contract OLAS is ERC20 { + 18 | | event MinterUpdated(address indexed minter); + 19 | | event OwnerUpdated(address indexed owner); + 20 | | + 21 | | // One year interval + 22 | | uint256 public constant oneYear = 1 days * 365; + 23 | | // Total supply cap for the first ten years (one billion OLAS tokens) + 24 | | uint256 public constant tenYearSupplyCap = 1_000_000_000e18; + 25 | | // Maximum annual inflation after first ten years + 26 | | uint256 public constant maxMintCapFraction = 2; + 27 | | // Initial timestamp of the token deployment + 28 | | uint256 public immutable timeLaunch; + 29 | | + 30 | | // Owner address + 31 | | address public owner; + 32 | | // Minter address + 33 | | address public minter; + 34 | | + 35 | | constructor() ERC20("Autonolas", "OLAS", 18) { + 36 | | owner = msg.sender; + 37 | | minter = msg.sender; + 38 | | timeLaunch = block.timestamp; + 39 | | } + 40 | | + 41 | | /// @dev Changes the owner address. + 42 | | /// @param newOwner Address of a new owner. + 43 | | function changeOwner(address newOwner) external { + 44 | | if (msg.sender != owner) { + 45 | | revert ManagerOnly(msg.sender, owner); + 46 | | } + 47 | | + 48 | | if (newOwner == address(0)) { + 49 | | revert ZeroAddress(); + 50 | | } + 51 | | + 52 | | owner = newOwner; + 53 | | emit OwnerUpdated(newOwner); + 54 | | } + 55 | | + 56 | | /// @dev Changes the minter address. + 57 | | /// @param newMinter Address of a new minter. + 58 | | function changeMinter(address newMinter) external { + 59 | | if (msg.sender != owner) { + 60 | | revert ManagerOnly(msg.sender, owner); + 61 | | } + 62 | | + 63 | | if (newMinter == address(0)) { + 64 | | revert ZeroAddress(); + 65 | | } + 66 | | + 67 | | minter = newMinter; + 68 | | emit MinterUpdated(newMinter); + 69 | | } + 70 | | + 71 | | /// @dev Mints OLAS tokens. + 72 | | /// @notice If the inflation control does not pass, the revert does not take place, as well as no action is performed. + 73 | | /// @param account Account address. + 74 | | /// @param amount OLAS token amount. + 75 | | function mint(address account, uint256 amount) external { + 76 | | // Access control + 77 | | if (msg.sender != minter) { + 78 | | revert ManagerOnly(msg.sender, minter); + 79 | | } + 80 | | + 81 | | // Check the inflation schedule and mint + 82 | | if (inflationControl(amount)) { + 83 | | _mint(account, amount); + 84 | | } + 85 | | } + 86 | | + 87 | | /// @dev Provides various checks for the inflation control. + 88 | | /// @notice The `<=` check is left as is for a better code readability. + 89 | | /// @param amount Amount of OLAS to mint. + 90 | | /// @return True if the amount request is within inflation boundaries. + 91 | | function inflationControl(uint256 amount) public view returns (bool) { + 92 | | uint256 remainder = inflationRemainder(); + 93 | | return (amount <= remainder); + 94 | | } + 95 | | + 96 | | /// @dev Gets the reminder of OLAS possible for the mint. + 97 | | /// @return remainder OLAS token remainder. + 98 | | function inflationRemainder() public view returns (uint256 remainder) { + 99 | | uint256 _totalSupply = totalSupply; + 100 | | // Current year + 101 | | uint256 numYears = (block.timestamp - timeLaunch) / oneYear; + 102 | | // Calculate maximum mint amount to date + 103 | | uint256 supplyCap = tenYearSupplyCap; + 104 | | // After 10 years, adjust supplyCap according to the yearly inflation % set in maxMintCapFraction + 105 | | if (numYears > 9) { + 106 | | // Number of years after ten years have passed (including ongoing ones) + 107 | | numYears -= 9; + 108 | | for (uint256 i = 0; i < numYears; ++i) { + 109 | | supplyCap += (supplyCap * maxMintCapFraction) / 100; + 110 | | } + 111 | | } + 112 | | // Check for the requested mint overflow + 113 | | remainder = supplyCap - _totalSupply; + 114 | | } + 115 | | + 116 | | /// @dev Burns OLAS tokens. + 117 | | /// @param amount OLAS token amount to burn. + 118 | | function burn(uint256 amount) external { + 119 | | _burn(msg.sender, amount); + 120 | | } + 121 | | + 122 | | /// @dev Decreases the allowance of another account over their tokens. + 123 | | /// @notice This implementation does not decrease spender allowance if the maximum allowance was granted. + 124 | | /// @notice The underflow condition is treated by the default code generation check. + 125 | | /// @param spender Account that tokens are approved for. + 126 | | /// @param amount Amount to decrease approval by. + 127 | | /// @return True if the operation succeeded. + 128 | | function decreaseAllowance(address spender, uint256 amount) external returns (bool) { + 129 | | uint256 spenderAllowance = allowance[msg.sender][spender]; + 130 | | + 131 | | if (spenderAllowance != type(uint256).max) { + 132 | | spenderAllowance -= amount; + 133 | | allowance[msg.sender][spender] = spenderAllowance; + 134 | | emit Approval(msg.sender, spender, spenderAllowance); + 135 | | } + 136 | | + 137 | | return true; + 138 | | } + 139 | | + 140 | | /// @dev Increases the allowance of another account over their tokens. + 141 | | /// @notice The overflow condition is treated by the default code generation check. + 142 | | /// @param spender Account that tokens are approved for. + 143 | | /// @param amount Amount to increase approval by. + 144 | | /// @return True if the operation succeeded. + 145 | | function increaseAllowance(address spender, uint256 amount) external returns (bool) { + 146 | | uint256 spenderAllowance = allowance[msg.sender][spender]; + 147 | | + 148 | | spenderAllowance += amount; + 149 | | allowance[msg.sender][spender] = spenderAllowance; + 150 | | emit Approval(msg.sender, spender, spenderAllowance); + 151 | | + 152 | | return true; + 153 | | } + 154 | | } + 155 | | + + +
+ +/home/andrey/valory/autonolas-governance/contracts/interfaces/IErrors.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity ^0.8.15; + 3 | | + 4 | | /// @dev Errors. + 5 | | interface IErrors { + 6 | | /// @dev Only `owner` has a privilege, but the `sender` was provided. + 7 | | /// @param sender Sender address. + 8 | | /// @param owner Required sender address as an owner. + 9 | | error OwnerOnly(address sender, address owner); + 10 | | + 11 | | /// @dev Provided zero address. + 12 | | error ZeroAddress(); + 13 | | + 14 | | /// @dev Zero value when it has to be different from zero. + 15 | | error ZeroValue(); + 16 | | + 17 | | /// @dev Non-zero value when it has to be zero. + 18 | | error NonZeroValue(); + 19 | | + 20 | | /// @dev Wrong length of two arrays. + 21 | | /// @param numValues1 Number of values in a first array. + 22 | | /// @param numValues2 Numberf of values in a second array. + 23 | | error WrongArrayLength(uint256 numValues1, uint256 numValues2); + 24 | | + 25 | | /// @dev Value overflow. + 26 | | /// @param provided Overflow value. + 27 | | /// @param max Maximum possible value. + 28 | | error Overflow(uint256 provided, uint256 max); + 29 | | + 30 | | /// @dev Token is non-transferable. + 31 | | /// @param account Token address. + 32 | | error NonTransferable(address account); + 33 | | + 34 | | /// @dev Token is non-delegatable. + 35 | | /// @param account Token address. + 36 | | error NonDelegatable(address account); + 37 | | + 38 | | /// @dev Insufficient token allowance. + 39 | | /// @param provided Provided amount. + 40 | | /// @param expected Minimum expected amount. + 41 | | error InsufficientAllowance(uint256 provided, uint256 expected); + 42 | | + 43 | | /// @dev No existing lock value is found. + 44 | | /// @param account Address that is checked for the locked value. + 45 | | error NoValueLocked(address account); + 46 | | + 47 | | /// @dev Locked value is not zero. + 48 | | /// @param account Address that is checked for the locked value. + 49 | | /// @param amount Locked amount. + 50 | | error LockedValueNotZero(address account, uint256 amount); + 51 | | + 52 | | /// @dev Value lock is expired. + 53 | | /// @param account Address that is checked for the locked value. + 54 | | /// @param deadline The lock expiration deadline. + 55 | | /// @param curTime Current timestamp. + 56 | | error LockExpired(address account, uint256 deadline, uint256 curTime); + 57 | | + 58 | | /// @dev Value lock is not expired. + 59 | | /// @param account Address that is checked for the locked value. + 60 | | /// @param deadline The lock expiration deadline. + 61 | | /// @param curTime Current timestamp. + 62 | | error LockNotExpired(address account, uint256 deadline, uint256 curTime); + 63 | | + 64 | | /// @dev Provided unlock time is incorrect. + 65 | | /// @param account Address that is checked for the locked value. + 66 | | /// @param minUnlockTime Minimal unlock time that can be set. + 67 | | /// @param providedUnlockTime Provided unlock time. + 68 | | error UnlockTimeIncorrect(address account, uint256 minUnlockTime, uint256 providedUnlockTime); + 69 | | + 70 | | /// @dev Provided unlock time is bigger than the maximum allowed. + 71 | | /// @param account Address that is checked for the locked value. + 72 | | /// @param maxUnlockTime Max unlock time that can be set. + 73 | | /// @param providedUnlockTime Provided unlock time. + 74 | | error MaxUnlockTimeReached(address account, uint256 maxUnlockTime, uint256 providedUnlockTime); + 75 | | + 76 | | /// @dev Provided block number is incorrect (has not been processed yet). + 77 | | /// @param providedBlockNumber Provided block number. + 78 | | /// @param actualBlockNumber Actual block number. + 79 | | error WrongBlockNumber(uint256 providedBlockNumber, uint256 actualBlockNumber); + 80 | | } + 81 | | + + +
+ +/home/andrey/valory/autonolas-governance/contracts/test/EchidnaVoteWeightingAssert.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity ^0.8.23; + 3 | | + 4 | | import "../OLAS.sol"; + 5 | | import "../veOLAS.sol"; + 6 | | import "./VoteWeightingFuzzing.sol"; + 7 | | + 8 | | + 9 | *r | contract EchidnaVoteWeightingAssert { + 10 | | OLAS olas; + 11 | | veOLAS ve; + 12 | | VoteWeightingFuzzing vw; + 13 | | + 14 | | uint256 constant oneOLASBalance = 1 ether; + 15 | * | uint256 constant fourYear = 4 * 365 * 86400; + 16 | | uint256 constant oneYear = 1 * 365 * 86400; + 17 | * | uint256 constant maxVoteWeight = 10000; + 18 | | uint64 constant WEEK = 1 weeks; + 19 | * | uint256 constant oneOLAS = 1 ether; + 20 | | uint256 constant oneMLN = 1_000_000; + 21 | | uint256 ts; + 22 | | + 23 | | // msg.sender in Echidna + 24 | | address[3] private senders = [ address(0x10000), address(0x20000), address(0x30000) ]; + 25 | | + 26 | | constructor() payable { + 27 | | olas = new OLAS(); + 28 | | address aolas = address(olas); + 29 | | ve = new veOLAS(aolas, "Voting Escrow OLAS", "veOLAS"); + 30 | | address ave = address(ve); + 31 | | vw = new VoteWeightingFuzzing(ave); + 32 | | olas.mint(address(this),oneOLAS*oneMLN); + 33 | | } + 34 | | + 35 | | // voteForNomineeWeights_assert(0xdeadbeef,1,0,4495678220902361,1124857) + 36 | * | function voteForNomineeWeights_assert(address account, uint32 chainId, uint16 weight, uint256 amount, uint32 unlockTime) external { + 37 | * | require(block.timestamp > 0); + 38 | *r | require(block.timestamp > ts); + 39 | *r | require(unlockTime < fourYear); + 40 | *r | require(weight < maxVoteWeight); + 41 | *r | require(amount < 100 * oneOLAS); + 42 | * | uint256 balanceOf = olas.balanceOf(address(this)); + 43 | * | assert(balanceOf > amount); + 44 | * | (uint128 initialAmount,) = ve.mapLockedBalances(address(this)); + 45 | * | if (initialAmount == 0) { + 46 | * | olas.approve(address(ve), amount); + 47 | *r | ve.createLock(amount, unlockTime); + 48 | * | (uint128 lockedAmount,) = ve.mapLockedBalances(address(this)); + 49 | * | assert(lockedAmount > 0); + 50 | * | } else { + 51 | * | (uint128 lockedAmount,) = ve.mapLockedBalances(address(this)); + 52 | * | assert(lockedAmount > 0); + 53 | | } + 54 | *r | vw.addNomineeEVM(account, chainId); + 55 | * | bytes32 nominee = bytes32(uint256(uint160(account))); + 56 | * | uint256 id = vw.getNomineeId(nominee, chainId); + 57 | * | uint256 num = vw.getNumNominees(); + 58 | * | assert(id > 0); + 59 | * | assert(num > 0); + 60 | * | vw.setCallVoteForNomineeWeights(false); + 61 | * | bool beforeAfterCall = vw.callVoteForNomineeWeights(); + 62 | * | assert(beforeAfterCall == false); + 63 | *r | vw.voteForNomineeWeights(nominee, chainId, weight); + 64 | * | bool stateAfterCall = vw.callVoteForNomineeWeights(); + 65 | * | if(stateAfterCall == true) { + 66 | | uint256 lts = vw.getlastUserVote(nominee,chainId); + 67 | | assert(lts > 0); + 68 | | } + 69 | * | ts = block.timestamp; // next timestamp > timestamp + 70 | * | vw.checkpointNominee(nominee, chainId); + 71 | | } + 72 | | + 73 | | } + 74 | | + 75 | | + + +
+ +/home/andrey/valory/autonolas-governance/contracts/test/VoteWeightingFuzzing.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity ^0.8.23; + 3 | | + 4 | | // Dispenser interface + 5 | | interface IDispenser { + 6 | | /// @dev Records nominee addition in dispenser. + 7 | | /// @param nomineeHash Nominee hash. + 8 | | function addNominee(bytes32 nomineeHash) external; + 9 | | + 10 | | /// @dev Records nominee removal. + 11 | | /// @param nomineeHash Nominee hash. + 12 | | function removeNominee(bytes32 nomineeHash) external; + 13 | | } + 14 | | + 15 | | // veOLAS interface + 16 | | interface IVEOLAS { + 17 | | // Structure for voting escrow points + 18 | | // The struct size is two storage slots of 2 * uint256 (128 + 128 + 64 + 64 + 128) + 19 | | struct PointVoting { + 20 | | // w(i) = at + b (bias) + 21 | | int128 bias; + 22 | | // dw / dt = a (slope) + 23 | | int128 slope; + 24 | | // Timestamp. It will never practically be bigger than 2^64 - 1 + 25 | | uint64 ts; + 26 | | // Block number. It will not be bigger than the timestamp + 27 | | uint64 blockNumber; + 28 | | // Token amount. It will never practically be bigger. Initial OLAS cap is 1 bn tokens, or 1e27. + 29 | | // After 10 years, the inflation rate is 2% per year. It would take 1340+ years to reach 2^128 - 1 + 30 | | uint128 balance; + 31 | | } + 32 | | + 33 | | /// @dev Gets the `account`'s lock end time. + 34 | | /// @param account Account address. + 35 | | /// @return unlockTime Lock end time. + 36 | | function lockedEnd(address account) external view returns (uint256 unlockTime); + 37 | | + 38 | | /// @dev Gets the most recently recorded user point for `account`. + 39 | | /// @param account Account address. + 40 | | /// @return pv Last checkpoint. + 41 | | function getLastUserPoint(address account) external view returns (PointVoting memory pv); + 42 | | } + 43 | | + 44 | | /// @dev Only `owner` has a privilege, but the `sender` was provided. + 45 | | /// @param sender Sender address. + 46 | | /// @param owner Required sender address as an owner. + 47 | | error OwnerOnly(address sender, address owner); + 48 | | + 49 | | /// @dev Provided zero address. + 50 | | error ZeroAddressVW(); + 51 | | + 52 | | /// @dev Zero value when it has to be different from zero. + 53 | | error ZeroValue(); + 54 | | + 55 | | /// @dev Wrong length of two arrays. + 56 | | /// @param numValues1 Number of values in a first array. + 57 | | /// @param numValues2 Number of values in a second array. + 58 | | error WrongArrayLength(uint256 numValues1, uint256 numValues2); + 59 | | + 60 | | /// @dev Value overflow. + 61 | | /// @param provided Overflow value. + 62 | | /// @param max Maximum possible value. + 63 | | error Overflow(uint256 provided, uint256 max); + 64 | | + 65 | | /// @dev Underflow value. + 66 | | /// @param provided Provided value. + 67 | | /// @param expected Minimum expected value. + 68 | | error Underflow(uint256 provided, uint256 expected); + 69 | | + 70 | | /// @dev Nominee does not exist. + 71 | | /// @param account Nominee account address. + 72 | | /// @param chainId Nominee chain Id. + 73 | | error NomineeDoesNotExist(bytes32 account, uint256 chainId); + 74 | | + 75 | | /// @dev Nominee already exists. + 76 | | /// @param account Nominee account address. + 77 | | /// @param chainId Nominee chain Id. + 78 | | error NomineeAlreadyExists(bytes32 account, uint256 chainId); + 79 | | + 80 | | /// @dev Value lock is expired. + 81 | | /// @param account Address that is checked for the locked value. + 82 | | /// @param deadline The lock expiration deadline. + 83 | | /// @param curTime Current timestamp. + 84 | | error LockExpired(address account, uint256 deadline, uint256 curTime); + 85 | | + 86 | | /// @dev The vote has been performed already. + 87 | | /// @param voter Voter address. + 88 | | /// @param curTime Current time. + 89 | | /// @param nextAllowedVotingTime Next allowed voting time. + 90 | | error VoteTooOften(address voter, uint256 curTime, uint256 nextAllowedVotingTime); + 91 | | + 92 | | /// @dev Nominee is not in the removed nominee map. + 93 | | /// @param account Nominee account address. + 94 | | /// @param chainId Nominee chain Id. + 95 | | error NomineeNotRemoved(bytes32 account, uint256 chainId); + 96 | | + 97 | | /// @dev Nominee is in the removed nominee map. + 98 | | /// @param account Nominee account address. + 99 | | /// @param chainId Nominee chain Id. + 100 | | error NomineeRemoved(bytes32 account, uint256 chainId); + 101 | | + 102 | | // Point struct + 103 | | struct Point { + 104 | | uint256 bias; + 105 | | uint256 slope; + 106 | | } + 107 | | + 108 | | // Voted slope struct + 109 | | struct VotedSlope { + 110 | | uint256 slope; + 111 | | uint256 power; + 112 | | uint256 end; + 113 | | } + 114 | | + 115 | | // Nominee struct + 116 | | struct Nominee { + 117 | | bytes32 account; + 118 | | uint256 chainId; + 119 | | } + 120 | | + 121 | | + 122 | | /// @title VoteWeighting - Smart contract for Vote Weighting with specific nominees composed of address and chain Id + 123 | | /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz> + 124 | | /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz> + 125 | | /// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz> + 126 | * | contract VoteWeightingFuzzing { + 127 | | event OwnerUpdated(address indexed owner); + 128 | | event VoteForNominee(address indexed user, bytes32 indexed nominee, uint256 chainId, uint256 weight); + 129 | | event AddNominee(bytes32 indexed account, uint256 chainId, uint256 id); + 130 | | event RemoveNominee(bytes32 indexed account, uint256 chainId, uint256 newSum); + 131 | | + 132 | | // 7 * 86400 seconds - all future times are rounded by week + 133 | * | uint256 public constant WEEK = 604_800; + 134 | | // Cannot change weight votes more often than once in 10 days + 135 | * | uint256 public constant WEIGHT_VOTE_DELAY = 864_000; + 136 | | // Max weight amount + 137 | * | uint256 public constant MAX_WEIGHT = 10_000; + 138 | | // Maximum chain Id as per EVM specs + 139 | * | uint256 public constant MAX_EVM_CHAIN_ID = type(uint64).max / 2 - 36; + 140 | | // veOLAS contract address + 141 | | address public immutable ve; + 142 | | // Contract owner address + 143 | | address public owner; + 144 | | // Dispenser contract + 145 | | address public dispenser; + 146 | | + 147 | | // Set of Nominee structs + 148 | | Nominee[] public setNominees; + 149 | | // Mapping of hash(Nominee struct) => nominee Id + 150 | | mapping(bytes32 => uint256) public mapNomineeIds; + 151 | | // Mapping of hash(Nominee struct) => previously removed nominee flag + 152 | | mapping(bytes32 => bool) public mapRemovedNominees; + 153 | | + 154 | | // user -> hash(Nominee struct) -> VotedSlope + 155 | | mapping(address => mapping(bytes32 => VotedSlope)) public voteUserSlopes; + 156 | | // Total vote power used by user + 157 | | mapping(address => uint256) public voteUserPower; + 158 | | // Last user vote's timestamp for each hash(Nominee struct) + 159 | | mapping(address => mapping(bytes32 => uint256)) public lastUserVote; + 160 | | + 161 | | // Past and scheduled points for nominee weight, sum of weights per type, total weight + 162 | | // Point is for bias+slope + 163 | | // changes_* are for changes in slope + 164 | | // time_* are for the last change timestamp + 165 | | // timestamps are rounded to whole weeks + 166 | | + 167 | | // hash(Nominee struct) -> time -> Point + 168 | | mapping(bytes32 => mapping(uint256 => Point)) public pointsWeight; + 169 | | // hash(Nominee struct) -> time -> slope + 170 | | mapping(bytes32 => mapping(uint256 => uint256)) public changesWeight; + 171 | | // hash(Nominee struct) -> last scheduled time (next week) + 172 | | mapping(bytes32 => uint256) public timeWeight; + 173 | | + 174 | | // time -> Point + 175 | | mapping(uint256 => Point) public pointsSum; + 176 | | // time -> slope + 177 | | mapping(uint256 => uint256) public changesSum; + 178 | | // last scheduled time (next week) + 179 | | uint256 public timeSum; + 180 | | // for fuzzing + 181 | * | bool public callVoteForNomineeWeights = false; + 182 | | + 183 | | /// @dev Contract constructor. + 184 | | /// @param _ve Voting Escrow contract address. + 185 | | constructor(address _ve) { + 186 | | // Check for the zero address + 187 | | if (_ve == address(0)) { + 188 | | revert ZeroAddressVW(); + 189 | | } + 190 | | + 191 | | // Set initial parameters + 192 | | owner = msg.sender; + 193 | | ve = _ve; + 194 | | timeSum = block.timestamp / WEEK * WEEK; + 195 | | setNominees.push(Nominee(bytes32(0), 0)); + 196 | | } + 197 | | + 198 | | /// @dev Fill sum of nominee weights for the same type week-over-week for missed checkins and return the sum for the future week. + 199 | | /// @return Sum of weights. + 200 | * | function _getSum() internal returns (uint256) { + 201 | | // t is always > 0 as it is set in the constructor + 202 | * | uint256 t = timeSum; + 203 | * | Point memory pt = pointsSum[t]; + 204 | * | for (uint256 i = 0; i < 500; i++) { + 205 | * | if (t > block.timestamp) { + 206 | * | break; + 207 | | } + 208 | * | t += WEEK; + 209 | * | uint256 dBias = pt.slope * WEEK; + 210 | * | if (pt.bias > dBias) { + 211 | * | pt.bias -= dBias; + 212 | * | uint256 dSlope = changesSum[t]; + 213 | * | pt.slope -= dSlope; + 214 | | } else { + 215 | * | pt.bias = 0; + 216 | * | pt.slope = 0; + 217 | | } + 218 | | + 219 | * | pointsSum[t] = pt; + 220 | * | if (t > block.timestamp) { + 221 | * | timeSum = t; + 222 | | } + 223 | | } + 224 | * | return pt.bias; + 225 | | } + 226 | | + 227 | | /// @dev Fill historic nominee weights week-over-week for missed checkins and return the total for the future week. + 228 | | /// @param account Nominee account address in bytes32 form. + 229 | | /// @param chainId Nominee chain Id. + 230 | | /// @return Nominee weight. + 231 | * | function _getWeight(bytes32 account, uint256 chainId) internal returns (uint256) { + 232 | | // Construct the nominee struct + 233 | * | Nominee memory nominee = Nominee(account, chainId); + 234 | | + 235 | | // Check that the nominee exists or has been removed + 236 | * | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 237 | * | if (!mapRemovedNominees[nomineeHash] && mapNomineeIds[nomineeHash] == 0) { + 238 | | revert NomineeDoesNotExist(account, chainId); + 239 | | } + 240 | | + 241 | | // t is always > 0 as it is set during the addNominee() call + 242 | * | uint256 t = timeWeight[nomineeHash]; + 243 | * | Point memory pt = pointsWeight[nomineeHash][t]; + 244 | * | for (uint256 i = 0; i < 500; i++) { + 245 | * | if (t > block.timestamp) { + 246 | * | break; + 247 | | } + 248 | | t += WEEK; + 249 | | uint256 dBias = pt.slope * WEEK; + 250 | | if (pt.bias > dBias) { + 251 | | pt.bias -= dBias; + 252 | | uint256 dSlope = changesWeight[nomineeHash][t]; + 253 | | pt.slope -= dSlope; + 254 | | } else { + 255 | | pt.bias = 0; + 256 | | pt.slope = 0; + 257 | | } + 258 | | + 259 | | pointsWeight[nomineeHash][t] = pt; + 260 | | if (t > block.timestamp) { + 261 | | timeWeight[nomineeHash] = t; + 262 | | } + 263 | | } + 264 | * | return pt.bias; + 265 | | } + 266 | | + 267 | | /// @dev Add nominee address along with the chain Id. + 268 | | /// @param nominee Nominee account address and chainId. + 269 | * | function _addNominee(Nominee memory nominee) internal { + 270 | | // Check for the nominee existence + 271 | * | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 272 | * | if (mapNomineeIds[nomineeHash] > 0) { + 273 | * | revert NomineeAlreadyExists(nominee.account, nominee.chainId); + 274 | | } + 275 | | + 276 | | // Check for the previously removed nominee + 277 | * | if (mapRemovedNominees[nomineeHash]) { + 278 | | revert NomineeRemoved(nominee.account, nominee.chainId); + 279 | | } + 280 | | + 281 | * | uint256 id = setNominees.length; + 282 | * | mapNomineeIds[nomineeHash] = id; + 283 | | // Push the nominee into the list + 284 | * | setNominees.push(nominee); + 285 | | + 286 | * | uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + 287 | * | timeWeight[nomineeHash] = nextTime; + 288 | | + 289 | | // Enable nominee in dispenser, if applicable + 290 | * | address localDispenser = dispenser; + 291 | * | if (localDispenser != address(0)) { + 292 | | IDispenser(localDispenser).addNominee(nomineeHash); + 293 | | } + 294 | | + 295 | * | emit AddNominee(nominee.account, nominee.chainId, id); + 296 | | } + 297 | | + 298 | | /// @dev Add EVM nominee address along with the chain Id. + 299 | | /// @param account Address of the nominee. + 300 | | /// @param chainId Chain Id. + 301 | * | function addNomineeEVM(address account, uint256 chainId) external { + 302 | | // Check for the zero address + 303 | * | if (account == address(0)) { + 304 | * | revert ZeroAddressVW(); + 305 | | } + 306 | | + 307 | | // Check for zero chain Id + 308 | * | if (chainId == 0) { + 309 | * | revert ZeroValue(); + 310 | | } + 311 | | + 312 | | // Check for the chain Id overflow + 313 | * | if (chainId > MAX_EVM_CHAIN_ID) { + 314 | | revert Overflow(chainId, MAX_EVM_CHAIN_ID); + 315 | | } + 316 | | + 317 | * | Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); + 318 | | + 319 | | // Record nominee instance + 320 | * | _addNominee(nominee); + 321 | | } + 322 | | + 323 | | /// @dev Add Non-EVM nominee address along with the chain Id. + 324 | | /// @param account Address of the nominee in byte32 standard. + 325 | | /// @param chainId Chain Id. + 326 | | function addNomineeNonEVM(bytes32 account, uint256 chainId) external { + 327 | | // Check for the zero address + 328 | | if (account == bytes32(0)) { + 329 | | revert ZeroAddressVW(); + 330 | | } + 331 | | + 332 | | // Check for the chain Id underflow + 333 | | if (MAX_EVM_CHAIN_ID >= chainId) { + 334 | | revert Underflow(chainId, MAX_EVM_CHAIN_ID + 1); + 335 | | } + 336 | | + 337 | | Nominee memory nominee = Nominee(account, chainId); + 338 | | + 339 | | // Record nominee instance + 340 | | _addNominee(nominee); + 341 | | } + 342 | | + 343 | | /// @dev Changes the owner address. + 344 | | /// @param newOwner Address of a new owner. + 345 | | function changeOwner(address newOwner) external { + 346 | | // Check for the contract ownership + 347 | | if (msg.sender != owner) { + 348 | | revert OwnerOnly(msg.sender, owner); + 349 | | } + 350 | | + 351 | | // Check for the zero address + 352 | | if (newOwner == address(0)) { + 353 | | revert ZeroAddressVW(); + 354 | | } + 355 | | + 356 | | owner = newOwner; + 357 | | emit OwnerUpdated(newOwner); + 358 | | } + 359 | | + 360 | | /// @dev Changes the dispenser contract address. + 361 | | /// @notice Dispenser can a zero address if the contract needs to serve a general purpose. + 362 | | /// @param newDispenser New dispenser contract address. + 363 | | function changeDispenser(address newDispenser) external { + 364 | | // Check for the contract ownership + 365 | | if (msg.sender != owner) { + 366 | | revert OwnerOnly(msg.sender, owner); + 367 | | } + 368 | | + 369 | | dispenser = newDispenser; + 370 | | } + 371 | | + 372 | | /// @dev Checkpoint to fill data common for all nominees. + 373 | | function checkpoint() external { + 374 | | _getSum(); + 375 | | } + 376 | | + 377 | | /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees. + 378 | | /// @param account Address of the nominee. + 379 | | /// @param chainId Chain Id. + 380 | * | function checkpointNominee(bytes32 account, uint256 chainId) external { + 381 | * | _getWeight(account, chainId); + 382 | * | _getSum(); + 383 | | } + 384 | | + 385 | | /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18) and a sum of weights. + 386 | | /// Inflation which will be received by it is inflation_rate * relativeWeight / 1e18. + 387 | | /// @param account Address of the nominee in byte32 standard. + 388 | | /// @param chainId Chain Id. + 389 | | /// @param time Relative weight at the specified timestamp in the past or present. + 390 | | /// @return weight Value of relative weight normalized to 1e18. + 391 | | /// @return totalSum Sum of nominee weights. + 392 | | function _nomineeRelativeWeight( + 393 | | bytes32 account, + 394 | | uint256 chainId, + 395 | | uint256 time + 396 | | ) internal view returns (uint256 weight, uint256 totalSum) { + 397 | | uint256 t = time / WEEK * WEEK; + 398 | | totalSum = pointsSum[t].bias; + 399 | | + 400 | | Nominee memory nominee = Nominee(account, chainId); + 401 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 402 | | + 403 | | if (totalSum > 0) { + 404 | | uint256 nomineeWeight = pointsWeight[nomineeHash][t].bias; + 405 | | weight = 1e18 * nomineeWeight / totalSum; + 406 | | } + 407 | | } + 408 | | + 409 | | /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 and the sum of weights. + 410 | | /// (e.g. 1.0 == 1e18). Inflation which will be received by it is + 411 | | /// inflation_rate * relativeWeight / 1e18. + 412 | | /// @param account Address of the nominee in bytes32 form. + 413 | | /// @param chainId Chain Id. + 414 | | /// @param time Relative weight at the specified timestamp in the past or present. + 415 | | /// @return weight Value of relative weight normalized to 1e18. + 416 | | /// @return totalSum Sum of nominee weights. + 417 | | function nomineeRelativeWeight( + 418 | | bytes32 account, + 419 | | uint256 chainId, + 420 | | uint256 time + 421 | | ) external view returns (uint256 weight, uint256 totalSum) { + 422 | | (weight, totalSum) = _nomineeRelativeWeight(account, chainId, time); + 423 | | } + 424 | | + 425 | | /// @dev Get nominee weight normalized to 1e18 and also fill all the unfilled values for type and nominee records. + 426 | | /// Also, get the total sum of all the nominee weights. + 427 | | /// @notice Any address can call, however nothing is recorded if the values are filled already. + 428 | | /// @param account Address of the nominee in bytes32 form. + 429 | | /// @param chainId Chain Id. + 430 | | /// @param time Relative weight at the specified timestamp in the past or present. + 431 | | /// @return weight Value of relative weight normalized to 1e18. + 432 | | /// @return totalSum Sum of nominee weights. + 433 | | function nomineeRelativeWeightWrite( + 434 | | bytes32 account, + 435 | | uint256 chainId, + 436 | | uint256 time + 437 | | ) external returns (uint256 weight, uint256 totalSum) { + 438 | | _getWeight(account, chainId); + 439 | | _getSum(); + 440 | | (weight, totalSum) = _nomineeRelativeWeight(account, chainId, time); + 441 | | } + 442 | | + 443 | | /// @dev Allocate voting power for changing pool weights. + 444 | | /// @param account Address of the nominee the `msg.sender` votes for in bytes32 form. + 445 | | /// @param chainId Chain Id. + 446 | | /// @param weight Weight for a nominee in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0. + 447 | * | function voteForNomineeWeights(bytes32 account, uint256 chainId, uint256 weight) public { + 448 | | // Get the nominee hash + 449 | * | bytes32 nomineeHash = keccak256(abi.encode(Nominee(account, chainId))); + 450 | | + 451 | | // Check for the previously removed nominee + 452 | * | if (mapRemovedNominees[nomineeHash]) { + 453 | | revert NomineeRemoved(account, chainId); + 454 | | } + 455 | | + 456 | * | uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); + 457 | * | uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); + 458 | * | uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + 459 | | + 460 | | // Check for the lock end expiration + 461 | * | if (nextTime >= lockEnd) { + 462 | * | revert LockExpired(msg.sender, lockEnd, nextTime); + 463 | | } + 464 | | + 465 | | // Check for the weight number + 466 | * | if (weight > MAX_WEIGHT) { + 467 | | revert Overflow(weight, MAX_WEIGHT); + 468 | | } + 469 | | + 470 | | // Check for the last voting time + 471 | * | uint256 nextAllowedVotingTime = lastUserVote[msg.sender][nomineeHash] + WEIGHT_VOTE_DELAY; + 472 | * | if (nextAllowedVotingTime > block.timestamp) { + 473 | | revert VoteTooOften(msg.sender, block.timestamp, nextAllowedVotingTime); + 474 | | } + 475 | | + 476 | | // Prepare old and new slopes and biases + 477 | * | VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; + 478 | * | uint256 oldBias; + 479 | * | if (oldSlope.end > nextTime) { + 480 | | oldBias = oldSlope.slope * (oldSlope.end - nextTime); + 481 | | } + 482 | | + 483 | * | VotedSlope memory newSlope = VotedSlope({ + 484 | * | slope: slope * weight / MAX_WEIGHT, + 485 | * | end: lockEnd, + 486 | * | power: weight + 487 | | }); + 488 | | + 489 | * | uint256 newBias = newSlope.slope * (lockEnd - nextTime); + 490 | | + 491 | * | uint256 powerUsed = voteUserPower[msg.sender]; + 492 | * | powerUsed = powerUsed + newSlope.power - oldSlope.power; + 493 | * | voteUserPower[msg.sender] = powerUsed; + 494 | * | if (powerUsed > MAX_WEIGHT) { + 495 | * | revert Overflow(powerUsed, MAX_WEIGHT); + 496 | | } + 497 | | + 498 | | // Remove old and schedule new slope changes + 499 | | // Remove slope changes for old slopes + 500 | | // Schedule recording of initial slope for nextTime + 501 | * | pointsWeight[nomineeHash][nextTime].bias = _maxAndSub(_getWeight(account, chainId) + newBias, oldBias); + 502 | * | pointsSum[nextTime].bias = _maxAndSub(_getSum() + newBias, oldBias); + 503 | * | if (oldSlope.end > nextTime) { + 504 | | pointsWeight[nomineeHash][nextTime].slope = + 505 | | _maxAndSub(pointsWeight[nomineeHash][nextTime].slope + newSlope.slope, oldSlope.slope); + 506 | | pointsSum[nextTime].slope = _maxAndSub(pointsSum[nextTime].slope + newSlope.slope, oldSlope.slope); + 507 | | } else { + 508 | * | pointsWeight[nomineeHash][nextTime].slope += newSlope.slope; + 509 | * | pointsSum[nextTime].slope += newSlope.slope; + 510 | | } + 511 | * | if (oldSlope.end > block.timestamp) { + 512 | | // Cancel old slope changes if they still didn't happen + 513 | | changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; + 514 | | changesSum[oldSlope.end] -= oldSlope.slope; + 515 | | } + 516 | | // Add slope changes for new slopes + 517 | * | changesWeight[nomineeHash][newSlope.end] += newSlope.slope; + 518 | * | changesSum[newSlope.end] += newSlope.slope; + 519 | | + 520 | * | voteUserSlopes[msg.sender][nomineeHash] = newSlope; + 521 | | + 522 | | // Record last action time + 523 | * | lastUserVote[msg.sender][nomineeHash] = block.timestamp; + 524 | | + 525 | * | emit VoteForNominee(msg.sender, account, chainId, weight); + 526 | | } + 527 | | + 528 | | /// @dev Allocate voting power for changing pool weights in batch. + 529 | | /// @param accounts Set of nominee addresses in bytes32 form the `msg.sender` votes for. + 530 | | /// @param chainIds Set of corresponding chain Ids. + 531 | | /// @param weights Weights for a nominees in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0. + 532 | | function voteForNomineeWeightsBatch( + 533 | | bytes32[] memory accounts, + 534 | | uint256[] memory chainIds, + 535 | | uint256[] memory weights + 536 | | ) external { + 537 | | if (accounts.length != chainIds.length || accounts.length != weights.length) { + 538 | | revert WrongArrayLength(accounts.length, weights.length); + 539 | | } + 540 | | + 541 | | // Traverse all accounts and weights + 542 | | for (uint256 i = 0; i < accounts.length; ++i) { + 543 | | voteForNomineeWeights(accounts[i], chainIds[i], weights[i]); + 544 | | } + 545 | | } + 546 | | + 547 | * | function _maxAndSub(uint256 a, uint256 b) internal pure returns (uint256) { + 548 | * | return a > b ? a - b : 0; + 549 | | } + 550 | | + 551 | | /// @dev Removes nominee from the contract and zeros its weight. + 552 | | /// @param account Address of the nominee in bytes32 form. + 553 | | /// @param chainId Chain Id. + 554 | | function removeNominee(bytes32 account, uint256 chainId) external { + 555 | | // Check for the contract ownership + 556 | | if (msg.sender != owner) { + 557 | | revert OwnerOnly(owner, msg.sender); + 558 | | } + 559 | | + 560 | | // Get the nominee struct and hash + 561 | | Nominee memory nominee = Nominee(account, chainId); + 562 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 563 | | + 564 | | // Get the nominee id in the nominee set + 565 | | uint256 id = mapNomineeIds[nomineeHash]; + 566 | | if (id == 0) { + 567 | | revert NomineeDoesNotExist(account, chainId); + 568 | | } + 569 | | + 570 | | // Set nominee weight to zero + 571 | | uint256 oldWeight = _getWeight(account, chainId); + 572 | | uint256 oldSum = _getSum(); + 573 | | uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + 574 | | pointsWeight[nomineeHash][nextTime].bias = 0; + 575 | | timeWeight[nomineeHash] = nextTime; + 576 | | + 577 | | // Account for the the sum weight change + 578 | | uint256 newSum = oldSum - oldWeight; + 579 | | pointsSum[nextTime].bias = newSum; + 580 | | timeSum = nextTime; + 581 | | + 582 | | // Add to the removed nominee map + 583 | | mapRemovedNominees[nomineeHash] = true; + 584 | | + 585 | | // Remove nominee in dispenser, if applicable + 586 | | address localDispenser = dispenser; + 587 | | if (localDispenser != address(0)) { + 588 | | IDispenser(localDispenser).removeNominee(nomineeHash); + 589 | | } + 590 | | + 591 | | // Remove nominee from the map + 592 | | mapNomineeIds[nomineeHash] = 0; + 593 | | // Shuffle the current last nominee id in the set to be placed to the removed one + 594 | | nominee = setNominees[setNominees.length - 1]; + 595 | | nomineeHash = keccak256(abi.encode(nominee)); + 596 | | mapNomineeIds[nomineeHash] = id; + 597 | | setNominees[id] = nominee; + 598 | | // Pop the last element from the set + 599 | | setNominees.pop(); + 600 | | + 601 | | emit RemoveNominee(account, chainId, newSum); + 602 | | } + 603 | | + 604 | | /// @dev Retrieves user voting power from a removed nominee. + 605 | | /// @param account Address of the nominee in bytes32 form. + 606 | | /// @param chainId Chain Id. + 607 | | function retrieveRemovedNomineeVotingPower(bytes32 account, uint256 chainId) external { + 608 | | // Get the nominee struct and hash + 609 | | Nominee memory nominee = Nominee(account, chainId); + 610 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 611 | | + 612 | | // Check that the nominee is removed + 613 | | if (!mapRemovedNominees[nomineeHash]) { + 614 | | revert NomineeNotRemoved(account, chainId); + 615 | | } + 616 | | + 617 | | // Get the user old slope + 618 | | VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; + 619 | | if (oldSlope.power == 0) { + 620 | | revert ZeroValue(); + 621 | | } + 622 | | + 623 | | // Cancel old slope changes if they still didn't happen + 624 | | if (oldSlope.end > block.timestamp) { + 625 | | changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; + 626 | | changesSum[oldSlope.end] -= oldSlope.slope; + 627 | | } + 628 | | + 629 | | // Update the voting power + 630 | | uint256 powerUsed = voteUserPower[msg.sender]; + 631 | | powerUsed = powerUsed - oldSlope.power; + 632 | | voteUserPower[msg.sender] = powerUsed; + 633 | | delete voteUserSlopes[msg.sender][nomineeHash]; + 634 | | } + 635 | | + 636 | | /// @dev Get current nominee weight. + 637 | | /// @param account Address of the nominee in bytes32 form. + 638 | | /// @param chainId Chain Id. + 639 | | /// @return Nominee weight. + 640 | | function getNomineeWeight(bytes32 account, uint256 chainId) external view returns (uint256) { + 641 | | // Get the nominee struct and hash + 642 | | Nominee memory nominee = Nominee(account, chainId); + 643 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 644 | | + 645 | | return pointsWeight[nomineeHash][timeWeight[nomineeHash]].bias; + 646 | | } + 647 | | + 648 | | /// @dev Get sum of nominee weights. + 649 | | /// @return Sum of nominee weights. + 650 | | function getWeightsSum() external view returns (uint256) { + 651 | | return pointsSum[timeSum].bias; + 652 | | } + 653 | | + 654 | | /// @dev Get the total number of nominees. + 655 | | /// @notice The zero-th default nominee Id with id == 0 does not count. + 656 | | /// @return Total number of nominees. + 657 | * | function getNumNominees() external view returns (uint256) { + 658 | * | return setNominees.length - 1; + 659 | | } + 660 | | + 661 | | /// @dev Gets a full set of nominees. + 662 | | /// @notice The returned set includes the zero-th empty nominee instance. + 663 | | /// @return nominees Set of all the nominees in the contract. + 664 | | function getAllNominees() external view returns (Nominee[] memory nominees) { + 665 | | nominees = setNominees; + 666 | | } + 667 | | + 668 | | /// @dev Gets the nominee Id in the global nominees set. + 669 | | /// @param account Nominee address in bytes32 form. + 670 | | /// @param chainId Chain Id. + 671 | | /// @return id Nominee Id in the global set of Nominee struct values. + 672 | * | function getNomineeId(bytes32 account, uint256 chainId) external view returns (uint256 id) { + 673 | | // Get the nominee struct and hash + 674 | * | Nominee memory nominee = Nominee(account, chainId); + 675 | * | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 676 | | + 677 | * | id = mapNomineeIds[nomineeHash]; + 678 | | } + 679 | | + 680 | | /// @dev Get the nominee address and its corresponding chain Id. + 681 | | /// @notice The zero-th default nominee Id with id == 0 does not count. + 682 | | /// @param id Nominee Id in the global set of Nominee struct values. + 683 | | /// @return nominee Nominee address in bytes32 form and chain Id. + 684 | | function getNominee(uint256 id) external view returns (Nominee memory nominee) { + 685 | | // Get the total number of nominees in the contract + 686 | | uint256 totalNumNominees = setNominees.length - 1; + 687 | | // Check for the zero id or the overflow + 688 | | if (id == 0) { + 689 | | revert ZeroValue(); + 690 | | } else if (id > totalNumNominees) { + 691 | | revert Overflow(id, totalNumNominees); + 692 | | } + 693 | | + 694 | | nominee = setNominees[id]; + 695 | | } + 696 | | + 697 | | /// @dev Get the set of nominee addresses and corresponding chain Ids. + 698 | | /// @notice The zero-th default nominee Id with id == 0 does not count. + 699 | | /// @param startId Start Id of the nominee in the global set of Nominee struct values. + 700 | | /// @param numNominees Number of nominees to get. + 701 | | /// @return nominees Set of nominee accounts in bytes32 form and chain Ids. + 702 | | function getNominees( + 703 | | uint256 startId, + 704 | | uint256 numNominees + 705 | | ) external view returns (Nominee[] memory nominees) + 706 | | { + 707 | | // Check for the zero id or the overflow + 708 | | if (startId == 0 || numNominees == 0) { + 709 | | revert ZeroValue(); + 710 | | } + 711 | | + 712 | | // Get the last nominee Id requested + 713 | | uint256 endId = startId + numNominees; + 714 | | // Get the total number of nominees in the contract with the zero-th nominee + 715 | | uint256 totalNumNominees = setNominees.length; + 716 | | + 717 | | // Check for the overflow + 718 | | if (endId > totalNumNominees) { + 719 | | revert Overflow(endId, totalNumNominees); + 720 | | } + 721 | | + 722 | | // Allocate the nominee array + 723 | | nominees = new Nominee[](numNominees); + 724 | | + 725 | | // Traverse selected nominees + 726 | | for (uint256 i = 0; i < numNominees; ++i) { + 727 | | uint256 id = i + startId; + 728 | | // Get the nominee struct + 729 | | nominees[i] = setNominees[id]; + 730 | | } + 731 | | } + 732 | | + 733 | | /// @dev Gets next allowed voting time for selected nominees and voters. + 734 | | /// @notice The function does not check for repeated nominees and voters. + 735 | | /// @param accounts Set of nominee account addresses. + 736 | | /// @param chainIds Corresponding set of chain Ids. + 737 | | /// @param voters Corresponding set of voters for specified nominees. + 738 | | function getNextAllowedVotingTimes( + 739 | | bytes32[] memory accounts, + 740 | | uint256[] memory chainIds, + 741 | | address[] memory voters + 742 | | ) external view returns (uint256[] memory nextAllowedVotingTimes) { + 743 | | // Check array lengths + 744 | | if (accounts.length != chainIds.length || accounts.length != voters.length) { + 745 | | revert WrongArrayLength(accounts.length, chainIds.length); + 746 | | } + 747 | | + 748 | | // Allocate the times array + 749 | | nextAllowedVotingTimes = new uint256[](accounts.length); + 750 | | + 751 | | // Traverse nominees and get next available voting times + 752 | | for (uint256 i = 0; i < accounts.length; ++i) { + 753 | | // Get the nominee struct and hash + 754 | | Nominee memory nominee = Nominee(accounts[i], chainIds[i]); + 755 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 756 | | + 757 | | // Check for nominee existence + 758 | | if (mapNomineeIds[nomineeHash] == 0) { + 759 | | revert NomineeDoesNotExist(accounts[i], chainIds[i]); + 760 | | } + 761 | | + 762 | | // Calculate next allowed voting times + 763 | | nextAllowedVotingTimes[i] = lastUserVote[voters[i]][nomineeHash] + WEIGHT_VOTE_DELAY; + 764 | | } + 765 | | } + 766 | | + 767 | | /// @dev For fuzzing only + 768 | * | function setCallVoteForNomineeWeights(bool flag) external { + 769 | * | callVoteForNomineeWeights = flag; + 770 | | } + 771 | | + 772 | | /// @dev For fuzzing only + 773 | | function getlastUserVote(bytes32 _nominee, uint256 chainId) external view returns (uint256) { + 774 | | // account occupies first 160 bits + 775 | | Nominee memory nominee = Nominee(_nominee, chainId); + 776 | | bytes32 nomineeHash = keccak256(abi.encode(nominee)); + 777 | | + 778 | | return lastUserVote[msg.sender][nomineeHash]; + 779 | | } + 780 | | } + + +
+ +/home/andrey/valory/autonolas-governance/contracts/veOLAS.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity ^0.8.15; + 3 | | + 4 | | import "@openzeppelin/contracts/governance/utils/IVotes.sol"; + 5 | | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + 6 | | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + 7 | | import "./interfaces/IErrors.sol"; + 8 | | + 9 | | /** + 10 | | Votes have a weight depending on time, so that users are committed to the future of (whatever they are voting for). + 11 | | Vote weight decays linearly over time. Lock time cannot be more than `MAXTIME` (4 years). + 12 | | Voting escrow has time-weighted votes derived from the amount of tokens locked. The maximum voting power can be + 13 | | achieved with the longest lock possible. This way the users are incentivized to lock tokens for more time. + 14 | | # w ^ = amount * time_locked / MAXTIME + 15 | | # 1 + / + 16 | | # | / + 17 | | # | / + 18 | | # | / + 19 | | # |/ + 20 | | # 0 +--------+------> time + 21 | | # maxtime (4 years?) + 22 | | + 23 | | We cannot really do block numbers per se because slope is per time, not per block, and per block could be fairly bad + 24 | | because Ethereum changes its block times. What we can do is to extrapolate ***At functions. + 25 | | */ + 26 | | + 27 | | /// @title Voting Escrow OLAS - the workflow is ported from Curve Finance Vyper implementation + 28 | | /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz> + 29 | | /// Code ported from: https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy + 30 | | /// and: https://github.com/solidlyexchange/solidly/blob/master/contracts/ve.sol + 31 | | + 32 | | /* This VotingEscrow is based on the OLAS token that has the following specifications: + 33 | | * - For the first 10 years there will be the cap of 1 billion (1e27) tokens; + 34 | | * - After 10 years, the inflation rate is 2% per year. + 35 | | * The maximum number of tokens for each year then can be calculated from the formula: 2^n = 1e27 * (1.02)^x, + 36 | | * where n is the specified number of bits that is sufficient to store and not overflow the total supply, + 37 | | * and x is the number of years. We limit n by 128, thus it would take 1340+ years to reach that total supply. + 38 | | * The amount for each locker is eventually cannot overcome this number as well, and thus uint128 is sufficient. + 39 | | * + 40 | | * We then limit the time in seconds to last until the value of 2^64 - 1, or for the next 583+ billion years. + 41 | | * The number of blocks is essentially cannot be bigger than the number of seconds, and thus it is safe to assume + 42 | | * that uint64 for the number of blocks is also sufficient. + 43 | | * + 44 | | * We also limit the individual deposit amount to be no bigger than 2^96 - 1, or the value of total supply in 220+ years. + 45 | | * This limitation is dictated by the fact that there will be at least several accounts with locked tokens, and the + 46 | | * sum of all of them cannot be bigger than the total supply. Checking the limit of deposited / increased amount + 47 | | * allows us to perform the unchecked operation on adding the amounts. + 48 | | * + 49 | | * The rest of calculations throughout the contract do not go beyond specified limitations. The contract was checked + 50 | | * by echidna and the results can be found in the audit section of the repository. + 51 | | * + 52 | | * These specified limits allowed us to have storage-added structs to be bound by 2*256 and 1*256 bit sizes + 53 | | * respectively, thus limiting the gas amount compared to using bigger variable sizes. + 54 | | * + 55 | | * Note that after 220 years it is no longer possible to deposit / increase the locked amount to be bigger than 2^96 - 1. + 56 | | * It is going to be not safe to use this contract for governance after 1340 years. + 57 | | */ + 58 | | + 59 | | // Struct for storing balance and unlock time + 60 | | // The struct size is one storage slot of uint256 (128 + 64 + padding) + 61 | | struct LockedBalance { + 62 | | // Token amount. It will never practically be bigger. Initial OLAS cap is 1 bn tokens, or 1e27. + 63 | | // After 10 years, the inflation rate is 2% per year. It would take 1340+ years to reach 2^128 - 1 + 64 | | uint128 amount; + 65 | | // Unlock time. It will never practically be bigger + 66 | | uint64 endTime; + 67 | | } + 68 | | + 69 | | // Structure for voting escrow points + 70 | | // The struct size is two storage slots of 2 * uint256 (128 + 128 + 64 + 64 + 128) + 71 | | struct PointVoting { + 72 | | // w(i) = at + b (bias) + 73 | | int128 bias; + 74 | | // dw / dt = a (slope) + 75 | | int128 slope; + 76 | | // Timestamp. It will never practically be bigger than 2^64 - 1 + 77 | | uint64 ts; + 78 | | // Block number. It will not be bigger than the timestamp + 79 | | uint64 blockNumber; + 80 | | // Token amount. It will never practically be bigger. Initial OLAS cap is 1 bn tokens, or 1e27. + 81 | | // After 10 years, the inflation rate is 2% per year. It would take 1340+ years to reach 2^128 - 1 + 82 | | uint128 balance; + 83 | | } + 84 | | + 85 | | /// @notice This token supports the ERC20 interface specifications except for transfers and approvals. + 86 | * | contract veOLAS is IErrors, IVotes, IERC20, IERC165 { + 87 | | enum DepositType { + 88 | | DEPOSIT_FOR_TYPE, + 89 | | CREATE_LOCK_TYPE, + 90 | | INCREASE_LOCK_AMOUNT, + 91 | | INCREASE_UNLOCK_TIME + 92 | | } + 93 | | + 94 | | event Deposit(address indexed account, uint256 amount, uint256 locktime, DepositType depositType, uint256 ts); + 95 | | event Withdraw(address indexed account, uint256 amount, uint256 ts); + 96 | | event Supply(uint256 previousSupply, uint256 currentSupply); + 97 | | + 98 | | // 1 week time + 99 | * | uint64 internal constant WEEK = 1 weeks; + 100 | | // Maximum lock time (4 years) + 101 | * | uint256 internal constant MAXTIME = 4 * 365 * 86400; + 102 | | // Maximum lock time (4 years) in int128 + 103 | * | int128 internal constant IMAXTIME = 4 * 365 * 86400; + 104 | | // Number of decimals + 105 | | uint8 public constant decimals = 18; + 106 | | + 107 | | // Token address + 108 | | address public immutable token; + 109 | | // Total token supply + 110 | | uint256 public supply; + 111 | | // Mapping of account address => LockedBalance + 112 | * | mapping(address => LockedBalance) public mapLockedBalances; + 113 | | + 114 | | // Total number of economical checkpoints (starting from zero) + 115 | | uint256 public totalNumPoints; + 116 | | // Mapping of point Id => point + 117 | | mapping(uint256 => PointVoting) public mapSupplyPoints; + 118 | | // Mapping of account address => PointVoting[point Id] + 119 | | mapping(address => PointVoting[]) public mapUserPoints; + 120 | | // Mapping of time => signed slope change + 121 | | mapping(uint64 => int128) public mapSlopeChanges; + 122 | | + 123 | | // Voting token name + 124 | | string public name; + 125 | | // Voting token symbol + 126 | | string public symbol; + 127 | | + 128 | | /// @dev Contract constructor + 129 | | /// @param _token Token address. + 130 | | /// @param _name Token name. + 131 | | /// @param _symbol Token symbol. + 132 | | constructor(address _token, string memory _name, string memory _symbol) + 133 | | { + 134 | | token = _token; + 135 | | name = _name; + 136 | | symbol = _symbol; + 137 | | // Create initial point such that default timestamp and block number are not zero + 138 | | // See cast specification in the PointVoting structure + 139 | | mapSupplyPoints[0] = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0); + 140 | | } + 141 | | + 142 | | /// @dev Gets the most recently recorded user point for `account`. + 143 | | /// @param account Account address. + 144 | | /// @return pv Last checkpoint. + 145 | * | function getLastUserPoint(address account) external view returns (PointVoting memory pv) { + 146 | * | uint256 lastPointNumber = mapUserPoints[account].length; + 147 | * | if (lastPointNumber > 0) { + 148 | * | pv = mapUserPoints[account][lastPointNumber - 1]; + 149 | | } + 150 | | } + 151 | | + 152 | | /// @dev Gets the number of user points. + 153 | | /// @param account Account address. + 154 | | /// @return accountNumPoints Number of user points. + 155 | | function getNumUserPoints(address account) external view returns (uint256 accountNumPoints) { + 156 | | accountNumPoints = mapUserPoints[account].length; + 157 | | } + 158 | | + 159 | | /// @dev Gets the checkpoint structure at number `idx` for `account`. + 160 | | /// @notice The out of bound condition is treated by the default code generation check. + 161 | | /// @param account User wallet address. + 162 | | /// @param idx User point number. + 163 | | /// @return The requested checkpoint. + 164 | | function getUserPoint(address account, uint256 idx) external view returns (PointVoting memory) { + 165 | | return mapUserPoints[account][idx]; + 166 | | } + 167 | | + 168 | | /// @dev Record global and per-user data to checkpoint. + 169 | | /// @param account Account address. User checkpoint is skipped if the address is zero. + 170 | | /// @param oldLocked Previous locked amount / end lock time for the user. + 171 | | /// @param newLocked New locked amount / end lock time for the user. + 172 | | /// @param curSupply Current total supply (to avoid using a storage total supply variable) + 173 | * | function _checkpoint( + 174 | | address account, + 175 | | LockedBalance memory oldLocked, + 176 | | LockedBalance memory newLocked, + 177 | | uint128 curSupply + 178 | * | ) internal { + 179 | * | PointVoting memory uOld; + 180 | * | PointVoting memory uNew; + 181 | * | int128 oldDSlope; + 182 | * | int128 newDSlope; + 183 | * | uint256 curNumPoint = totalNumPoints; + 184 | | + 185 | * | if (account != address(0)) { + 186 | | // Calculate slopes and biases + 187 | | // Kept at zero when they have to + 188 | * | if (oldLocked.endTime > block.timestamp && oldLocked.amount > 0) { + 189 | | uOld.slope = int128(oldLocked.amount) / IMAXTIME; + 190 | | uOld.bias = uOld.slope * int128(uint128(oldLocked.endTime - uint64(block.timestamp))); + 191 | | } + 192 | * | if (newLocked.endTime > block.timestamp && newLocked.amount > 0) { + 193 | * | uNew.slope = int128(newLocked.amount) / IMAXTIME; + 194 | * | uNew.bias = uNew.slope * int128(uint128(newLocked.endTime - uint64(block.timestamp))); + 195 | | } + 196 | | + 197 | | // Reads values of scheduled changes in the slope + 198 | | // oldLocked.endTime can be in the past and in the future + 199 | | // newLocked.endTime can ONLY be in the FUTURE unless everything is expired: then zeros + 200 | * | oldDSlope = mapSlopeChanges[oldLocked.endTime]; + 201 | * | if (newLocked.endTime > 0) { + 202 | * | if (newLocked.endTime == oldLocked.endTime) { + 203 | | newDSlope = oldDSlope; + 204 | | } else { + 205 | * | newDSlope = mapSlopeChanges[newLocked.endTime]; + 206 | | } + 207 | | } + 208 | | } + 209 | | + 210 | * | PointVoting memory lastPoint; + 211 | * | if (curNumPoint > 0) { + 212 | | lastPoint = mapSupplyPoints[curNumPoint]; + 213 | | } else { + 214 | | // If no point is created yet, we take the actual time and block parameters + 215 | * | lastPoint = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0); + 216 | | } + 217 | * | uint64 lastCheckpoint = lastPoint.ts; + 218 | | // initialPoint is used for extrapolation to calculate the block number and save them + 219 | | // as we cannot figure that out in exact values from inside of the contract + 220 | * | PointVoting memory initialPoint = lastPoint; + 221 | * | uint256 block_slope; // dblock/dt + 222 | * | if (block.timestamp > lastPoint.ts) { + 223 | | // This 1e18 multiplier is needed for the numerator to be bigger than the denominator + 224 | | // We need to calculate this in > uint64 size (1e18 is > 2^59 multiplied by 2^64). + 225 | | block_slope = (1e18 * uint256(block.number - lastPoint.blockNumber)) / uint256(block.timestamp - lastPoint.ts); + 226 | | } + 227 | | // If last point is already recorded in this block, slope == 0, but we know the block already in this case + 228 | | // Go over weeks to fill in the history and (or) calculate what the current point is + 229 | * | { + 230 | | // The timestamp is rounded by a week and < 2^64-1 + 231 | * | uint64 tStep = (lastCheckpoint / WEEK) * WEEK; + 232 | * | for (uint256 i = 0; i < 255; ++i) { + 233 | | // Hopefully it won't happen that this won't get used in 5 years! + 234 | | // If it does, users will be able to withdraw but vote weight will be broken + 235 | | // This is always practically < 2^64-1 + 236 | | unchecked { + 237 | * | tStep += WEEK; + 238 | | } + 239 | * | int128 dSlope; + 240 | * | if (tStep > block.timestamp) { + 241 | * | tStep = uint64(block.timestamp); + 242 | | } else { + 243 | | dSlope = mapSlopeChanges[tStep]; + 244 | | } + 245 | * | lastPoint.bias -= lastPoint.slope * int128(int64(tStep - lastCheckpoint)); + 246 | * | lastPoint.slope += dSlope; + 247 | * | if (lastPoint.bias < 0) { + 248 | | // This could potentially happen, but fuzzer didn't find available "real" combinations + 249 | | lastPoint.bias = 0; + 250 | | } + 251 | * | if (lastPoint.slope < 0) { + 252 | | // This cannot happen - just in case. Again, fuzzer didn't reach this + 253 | | lastPoint.slope = 0; + 254 | | } + 255 | * | lastCheckpoint = tStep; + 256 | * | lastPoint.ts = tStep; + 257 | | // After division by 1e18 the uint64 size can be reclaimed + 258 | * | lastPoint.blockNumber = initialPoint.blockNumber + uint64((block_slope * uint256(tStep - initialPoint.ts)) / 1e18); + 259 | * | lastPoint.balance = initialPoint.balance; + 260 | | // In order for the overflow of total number of economical checkpoints (starting from zero) + 261 | | // The _checkpoint() call must happen n >(2^256 -1)/255 or n > ~1e77/255 > ~1e74 times + 262 | | unchecked { + 263 | * | curNumPoint += 1; + 264 | | } + 265 | * | if (tStep == block.timestamp) { + 266 | * | lastPoint.blockNumber = uint64(block.number); + 267 | * | lastPoint.balance = curSupply; + 268 | * | break; + 269 | | } else { + 270 | | mapSupplyPoints[curNumPoint] = lastPoint; + 271 | | } + 272 | | } + 273 | | } + 274 | | + 275 | * | totalNumPoints = curNumPoint; + 276 | | + 277 | | // Now mapSupplyPoints is filled until current time + 278 | * | if (account != address(0)) { + 279 | | // If last point was in this block, the slope change has been already applied. In such case we have 0 slope(s) + 280 | * | lastPoint.slope += (uNew.slope - uOld.slope); + 281 | * | lastPoint.bias += (uNew.bias - uOld.bias); + 282 | * | if (lastPoint.slope < 0) { + 283 | | lastPoint.slope = 0; + 284 | | } + 285 | * | if (lastPoint.bias < 0) { + 286 | | lastPoint.bias = 0; + 287 | | } + 288 | | } + 289 | | + 290 | | // Record the last updated point + 291 | * | mapSupplyPoints[curNumPoint] = lastPoint; + 292 | | + 293 | * | if (account != address(0)) { + 294 | | // Schedule the slope changes (slope is going down) + 295 | | // We subtract new_user_slope from [newLocked.endTime] + 296 | | // and add old_user_slope to [oldLocked.endTime] + 297 | * | if (oldLocked.endTime > block.timestamp) { + 298 | | // oldDSlope was <something> - uOld.slope, so we cancel that + 299 | | oldDSlope += uOld.slope; + 300 | | if (newLocked.endTime == oldLocked.endTime) { + 301 | | oldDSlope -= uNew.slope; // It was a new deposit, not extension + 302 | | } + 303 | | mapSlopeChanges[oldLocked.endTime] = oldDSlope; + 304 | | } + 305 | | + 306 | * | if (newLocked.endTime > block.timestamp && newLocked.endTime > oldLocked.endTime) { + 307 | * | newDSlope -= uNew.slope; // old slope disappeared at this point + 308 | * | mapSlopeChanges[newLocked.endTime] = newDSlope; + 309 | | // else: we recorded it already in oldDSlope + 310 | | } + 311 | | // Now handle user history + 312 | * | uNew.ts = uint64(block.timestamp); + 313 | * | uNew.blockNumber = uint64(block.number); + 314 | * | uNew.balance = newLocked.amount; + 315 | * | mapUserPoints[account].push(uNew); + 316 | | } + 317 | | } + 318 | | + 319 | | /// @dev Record global data to checkpoint. + 320 | | function checkpoint() external { + 321 | | _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply)); + 322 | | } + 323 | | + 324 | | /// @dev Deposits and locks tokens for a specified account. + 325 | | /// @param account Target address for the locked amount. + 326 | | /// @param amount Amount to deposit. + 327 | | /// @param unlockTime New time when to unlock the tokens, or 0 if unchanged. + 328 | | /// @param lockedBalance Previous locked amount / end time. + 329 | | /// @param depositType Deposit type. + 330 | * | function _depositFor( + 331 | | address account, + 332 | | uint256 amount, + 333 | | uint256 unlockTime, + 334 | | LockedBalance memory lockedBalance, + 335 | | DepositType depositType + 336 | * | ) internal { + 337 | * | uint256 supplyBefore = supply; + 338 | * | uint256 supplyAfter; + 339 | | // Cannot overflow because the total supply << 2^128-1 + 340 | | unchecked { + 341 | * | supplyAfter = supplyBefore + amount; + 342 | * | supply = supplyAfter; + 343 | | } + 344 | | // Get the old locked data + 345 | * | LockedBalance memory oldLocked; + 346 | * | (oldLocked.amount, oldLocked.endTime) = (lockedBalance.amount, lockedBalance.endTime); + 347 | | // Adding to the existing lock, or if a lock is expired - creating a new one + 348 | | // This cannot be larger than the total supply + 349 | | unchecked { + 350 | * | lockedBalance.amount += uint128(amount); + 351 | | } + 352 | * | if (unlockTime > 0) { + 353 | * | lockedBalance.endTime = uint64(unlockTime); + 354 | | } + 355 | * | mapLockedBalances[account] = lockedBalance; + 356 | | + 357 | | // Possibilities: + 358 | | // Both oldLocked.endTime could be current or expired (>/< block.timestamp) + 359 | | // amount == 0 (extend lock) or amount > 0 (add to lock or extend lock) + 360 | | // lockedBalance.endTime > block.timestamp (always) + 361 | * | _checkpoint(account, oldLocked, lockedBalance, uint128(supplyAfter)); + 362 | * | if (amount > 0) { + 363 | | // OLAS is a solmate-based ERC20 token with optimized transferFrom() that either returns true or reverts + 364 | * | IERC20(token).transferFrom(msg.sender, address(this), amount); + 365 | | } + 366 | | + 367 | * | emit Deposit(account, amount, lockedBalance.endTime, depositType, block.timestamp); + 368 | * | emit Supply(supplyBefore, supplyAfter); + 369 | | } + 370 | | + 371 | | /// @dev Deposits `amount` tokens for `account` and adds to the lock. + 372 | | /// @dev Anyone (even a smart contract) can deposit for someone else, but + 373 | | /// cannot extend their locktime and deposit for a brand new user. + 374 | | /// @param account Account address. + 375 | | /// @param amount Amount to add. + 376 | | function depositFor(address account, uint256 amount) external { + 377 | | LockedBalance memory lockedBalance = mapLockedBalances[account]; + 378 | | // Check if the amount is zero + 379 | | if (amount == 0) { + 380 | | revert ZeroValue(); + 381 | | } + 382 | | // The locked balance must already exist + 383 | | if (lockedBalance.amount == 0) { + 384 | | revert NoValueLocked(account); + 385 | | } + 386 | | // Check the lock expiry + 387 | | if (lockedBalance.endTime < (block.timestamp + 1)) { + 388 | | revert LockExpired(msg.sender, lockedBalance.endTime, block.timestamp); + 389 | | } + 390 | | // Since in the _depositFor() we have the unchecked sum of amounts, this is needed to prevent unsafe behavior. + 391 | | // After 10 years, the inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1 total supply + 392 | | if (amount > type(uint96).max) { + 393 | | revert Overflow(amount, type(uint96).max); + 394 | | } + 395 | | + 396 | | _depositFor(account, amount, 0, lockedBalance, DepositType.DEPOSIT_FOR_TYPE); + 397 | | } + 398 | | + 399 | | /// @dev Deposits `amount` tokens for `msg.sender` and locks for `unlockTime`. + 400 | | /// @param amount Amount to deposit. + 401 | | /// @param unlockTime Time when tokens unlock, rounded down to a whole week. + 402 | * | function createLock(uint256 amount, uint256 unlockTime) external { + 403 | * | _createLockFor(msg.sender, amount, unlockTime); + 404 | | } + 405 | | + 406 | | /// @dev Deposits `amount` tokens for `account` and locks for `unlockTime`. + 407 | | /// @notice Tokens are taken from `msg.sender`'s balance. + 408 | | /// @param account Account address. + 409 | | /// @param amount Amount to deposit. + 410 | | /// @param unlockTime Time when tokens unlock, rounded down to a whole week. + 411 | | function createLockFor(address account, uint256 amount, uint256 unlockTime) external { + 412 | | // Check if the account address is zero + 413 | | if (account == address(0)) { + 414 | | revert ZeroAddress(); + 415 | | } + 416 | | + 417 | | _createLockFor(account, amount, unlockTime); + 418 | | } + 419 | | + 420 | | /// @dev Deposits `amount` tokens for `account` and locks for `unlockTime`. + 421 | | /// @notice Tokens are taken from `msg.sender`'s balance. + 422 | | /// @param account Account address. + 423 | | /// @param amount Amount to deposit. + 424 | | /// @param unlockTime Time when tokens unlock, rounded down to a whole week. + 425 | * | function _createLockFor(address account, uint256 amount, uint256 unlockTime) private { + 426 | | // Check if the amount is zero + 427 | * | if (amount == 0) { + 428 | * | revert ZeroValue(); + 429 | | } + 430 | | // Lock time is rounded down to weeks + 431 | | // Cannot practically overflow because block.timestamp + unlockTime (max 4 years) << 2^64-1 + 432 | | unchecked { + 433 | * | unlockTime = ((block.timestamp + unlockTime) / WEEK) * WEEK; + 434 | | } + 435 | * | LockedBalance memory lockedBalance = mapLockedBalances[account]; + 436 | | // The locked balance must be zero in order to start the lock + 437 | * | if (lockedBalance.amount > 0) { + 438 | | revert LockedValueNotZero(account, uint256(lockedBalance.amount)); + 439 | | } + 440 | | // Check for the lock time correctness + 441 | * | if (unlockTime < (block.timestamp + 1)) { + 442 | * | revert UnlockTimeIncorrect(account, block.timestamp, unlockTime); + 443 | | } + 444 | | // Check for the lock time not to exceed the MAXTIME + 445 | * | if (unlockTime > block.timestamp + MAXTIME) { + 446 | | revert MaxUnlockTimeReached(account, block.timestamp + MAXTIME, unlockTime); + 447 | | } + 448 | | // After 10 years, the inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1 total supply + 449 | * | if (amount > type(uint96).max) { + 450 | | revert Overflow(amount, type(uint96).max); + 451 | | } + 452 | | + 453 | * | _depositFor(account, amount, unlockTime, lockedBalance, DepositType.CREATE_LOCK_TYPE); + 454 | | } + 455 | | + 456 | | /// @dev Deposits `amount` additional tokens for `msg.sender` without modifying the unlock time. + 457 | | /// @param amount Amount of tokens to deposit and add to the lock. + 458 | | function increaseAmount(uint256 amount) external { + 459 | | LockedBalance memory lockedBalance = mapLockedBalances[msg.sender]; + 460 | | // Check if the amount is zero + 461 | | if (amount == 0) { + 462 | | revert ZeroValue(); + 463 | | } + 464 | | // The locked balance must already exist + 465 | | if (lockedBalance.amount == 0) { + 466 | | revert NoValueLocked(msg.sender); + 467 | | } + 468 | | // Check the lock expiry + 469 | | if (lockedBalance.endTime < (block.timestamp + 1)) { + 470 | | revert LockExpired(msg.sender, lockedBalance.endTime, block.timestamp); + 471 | | } + 472 | | // Check the max possible amount to add, that must be less than the total supply + 473 | | // After 10 years, the inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1 total supply + 474 | | if (amount > type(uint96).max) { + 475 | | revert Overflow(amount, type(uint96).max); + 476 | | } + 477 | | + 478 | | _depositFor(msg.sender, amount, 0, lockedBalance, DepositType.INCREASE_LOCK_AMOUNT); + 479 | | } + 480 | | + 481 | | /// @dev Extends the unlock time. + 482 | | /// @param unlockTime New tokens unlock time. + 483 | | function increaseUnlockTime(uint256 unlockTime) external { + 484 | | LockedBalance memory lockedBalance = mapLockedBalances[msg.sender]; + 485 | | // Cannot practically overflow because block.timestamp + unlockTime (max 4 years) << 2^64-1 + 486 | | unchecked { + 487 | | unlockTime = ((block.timestamp + unlockTime) / WEEK) * WEEK; + 488 | | } + 489 | | // The locked balance must already exist + 490 | | if (lockedBalance.amount == 0) { + 491 | | revert NoValueLocked(msg.sender); + 492 | | } + 493 | | // Check the lock expiry + 494 | | if (lockedBalance.endTime < (block.timestamp + 1)) { + 495 | | revert LockExpired(msg.sender, lockedBalance.endTime, block.timestamp); + 496 | | } + 497 | | // Check for the lock time correctness + 498 | | if (unlockTime < (lockedBalance.endTime + 1)) { + 499 | | revert UnlockTimeIncorrect(msg.sender, lockedBalance.endTime, unlockTime); + 500 | | } + 501 | | // Check for the lock time not to exceed the MAXTIME + 502 | | if (unlockTime > block.timestamp + MAXTIME) { + 503 | | revert MaxUnlockTimeReached(msg.sender, block.timestamp + MAXTIME, unlockTime); + 504 | | } + 505 | | + 506 | | _depositFor(msg.sender, 0, unlockTime, lockedBalance, DepositType.INCREASE_UNLOCK_TIME); + 507 | | } + 508 | | + 509 | | /// @dev Withdraws all tokens for `msg.sender`. Only possible if the lock has expired. + 510 | | function withdraw() external { + 511 | | LockedBalance memory lockedBalance = mapLockedBalances[msg.sender]; + 512 | | if (lockedBalance.endTime > block.timestamp) { + 513 | | revert LockNotExpired(msg.sender, lockedBalance.endTime, block.timestamp); + 514 | | } + 515 | | uint256 amount = uint256(lockedBalance.amount); + 516 | | + 517 | | mapLockedBalances[msg.sender] = LockedBalance(0, 0); + 518 | | uint256 supplyBefore = supply; + 519 | | uint256 supplyAfter; + 520 | | // The amount cannot be less than the total supply + 521 | | unchecked { + 522 | | supplyAfter = supplyBefore - amount; + 523 | | supply = supplyAfter; + 524 | | } + 525 | | // oldLocked can have either expired <= timestamp or zero end + 526 | | // lockedBalance has only 0 end + 527 | | // Both can have >= 0 amount + 528 | | _checkpoint(msg.sender, lockedBalance, LockedBalance(0, 0), uint128(supplyAfter)); + 529 | | + 530 | | emit Withdraw(msg.sender, amount, block.timestamp); + 531 | | emit Supply(supplyBefore, supplyAfter); + 532 | | + 533 | | // OLAS is a solmate-based ERC20 token with optimized transfer() that either returns true or reverts + 534 | | IERC20(token).transfer(msg.sender, amount); + 535 | | } + 536 | | + 537 | | /// @dev Finds a closest point that has a specified block number. + 538 | | /// @param blockNumber Block to find. + 539 | | /// @param account Account address for user points. + 540 | | /// @return point Point with the approximate index number for the specified block. + 541 | | /// @return minPointNumber Point number. + 542 | | function _findPointByBlock(uint256 blockNumber, address account) internal view + 543 | | returns (PointVoting memory point, uint256 minPointNumber) + 544 | | { + 545 | | // Get the last available point number + 546 | | uint256 maxPointNumber; + 547 | | if (account == address(0)) { + 548 | | maxPointNumber = totalNumPoints; + 549 | | } else { + 550 | | maxPointNumber = mapUserPoints[account].length; + 551 | | if (maxPointNumber == 0) { + 552 | | return (point, minPointNumber); + 553 | | } + 554 | | // Already checked for > 0 in this case + 555 | | unchecked { + 556 | | maxPointNumber -= 1; + 557 | | } + 558 | | } + 559 | | + 560 | | // Binary search that will be always enough for 128-bit numbers + 561 | | for (uint256 i = 0; i < 128; ++i) { + 562 | | if ((minPointNumber + 1) > maxPointNumber) { + 563 | | break; + 564 | | } + 565 | | uint256 mid = (minPointNumber + maxPointNumber + 1) / 2; + 566 | | + 567 | | // Choose the source of points + 568 | | if (account == address(0)) { + 569 | | point = mapSupplyPoints[mid]; + 570 | | } else { + 571 | | point = mapUserPoints[account][mid]; + 572 | | } + 573 | | + 574 | | if (point.blockNumber < (blockNumber + 1)) { + 575 | | minPointNumber = mid; + 576 | | } else { + 577 | | maxPointNumber = mid - 1; + 578 | | } + 579 | | } + 580 | | + 581 | | // Get the found point + 582 | | if (account == address(0)) { + 583 | | point = mapSupplyPoints[minPointNumber]; + 584 | | } else { + 585 | | point = mapUserPoints[account][minPointNumber]; + 586 | | } + 587 | | } + 588 | | + 589 | | /// @dev Gets the voting power for an `account` at time `ts`. + 590 | | /// @param account Account address. + 591 | | /// @param ts Time to get voting power at. + 592 | | /// @return vBalance Account voting power. + 593 | | function _balanceOfLocked(address account, uint64 ts) internal view returns (uint256 vBalance) { + 594 | | uint256 pointNumber = mapUserPoints[account].length; + 595 | | if (pointNumber > 0) { + 596 | | PointVoting memory uPoint = mapUserPoints[account][pointNumber - 1]; + 597 | | uPoint.bias -= uPoint.slope * int128(int64(ts) - int64(uPoint.ts)); + 598 | | if (uPoint.bias > 0) { + 599 | | vBalance = uint256(int256(uPoint.bias)); + 600 | | } + 601 | | } + 602 | | } + 603 | | + 604 | | /// @dev Gets the account balance in native token. + 605 | | /// @param account Account address. + 606 | | /// @return balance Account balance. + 607 | | function balanceOf(address account) public view override returns (uint256 balance) { + 608 | | balance = uint256(mapLockedBalances[account].amount); + 609 | | } + 610 | | + 611 | | /// @dev Gets the `account`'s lock end time. + 612 | | /// @param account Account address. + 613 | | /// @return unlockTime Lock end time. + 614 | * | function lockedEnd(address account) external view returns (uint256 unlockTime) { + 615 | * | unlockTime = uint256(mapLockedBalances[account].endTime); + 616 | | } + 617 | | + 618 | | /// @dev Gets the account balance at a specific block number. + 619 | | /// @param account Account address. + 620 | | /// @param blockNumber Block number. + 621 | | /// @return balance Account balance. + 622 | | function balanceOfAt(address account, uint256 blockNumber) external view returns (uint256 balance) { + 623 | | // Find point with the closest block number to the provided one + 624 | | (PointVoting memory uPoint, ) = _findPointByBlock(blockNumber, account); + 625 | | // If the block number at the point index is bigger than the specified block number, the balance was zero + 626 | | if (uPoint.blockNumber < (blockNumber + 1)) { + 627 | | balance = uint256(uPoint.balance); + 628 | | } + 629 | | } + 630 | | + 631 | | /// @dev Gets the voting power. + 632 | | /// @param account Account address. + 633 | | function getVotes(address account) public view override returns (uint256) { + 634 | | return _balanceOfLocked(account, uint64(block.timestamp)); + 635 | | } + 636 | | + 637 | | /// @dev Gets the block time adjustment for two neighboring points. + 638 | | /// @notice `blockNumber` must not be lower than the contract deployment block number, + 639 | | /// as the behavior and the return value is undefined. + 640 | | /// @param blockNumber Block number. + 641 | | /// @return point Point with the specified block number (or closest to it). + 642 | | /// @return blockTime Adjusted block time of the neighboring point. + 643 | | function _getBlockTime(uint256 blockNumber) internal view returns (PointVoting memory point, uint256 blockTime) { + 644 | | // Check the block number to be in the past or equal to the current block + 645 | | if (blockNumber > block.number) { + 646 | | revert WrongBlockNumber(blockNumber, block.number); + 647 | | } + 648 | | // Get the minimum historical point with the provided block number + 649 | | uint256 minPointNumber; + 650 | | (point, minPointNumber) = _findPointByBlock(blockNumber, address(0)); + 651 | | + 652 | | uint256 dBlock; + 653 | | uint256 dt; + 654 | | if (minPointNumber < totalNumPoints) { + 655 | | PointVoting memory pointNext = mapSupplyPoints[minPointNumber + 1]; + 656 | | dBlock = pointNext.blockNumber - point.blockNumber; + 657 | | dt = pointNext.ts - point.ts; + 658 | | } else { + 659 | | dBlock = block.number - point.blockNumber; + 660 | | dt = block.timestamp - point.ts; + 661 | | } + 662 | | blockTime = point.ts; + 663 | | if (dBlock > 0) { + 664 | | blockTime += (dt * (blockNumber - point.blockNumber)) / dBlock; + 665 | | } + 666 | | } + 667 | | + 668 | | /// @dev Gets voting power at a specific block number. + 669 | | /// @param account Account address. + 670 | | /// @param blockNumber Block number. + 671 | | /// @return balance Voting balance / power. + 672 | | function getPastVotes(address account, uint256 blockNumber) public view override returns (uint256 balance) { + 673 | | // Find the user point for the provided block number + 674 | | (PointVoting memory uPoint, ) = _findPointByBlock(blockNumber, account); + 675 | | + 676 | | // Get block time adjustment. + 677 | | (, uint256 blockTime) = _getBlockTime(blockNumber); + 678 | | + 679 | | // Calculate bias based on a block time + 680 | | uPoint.bias -= uPoint.slope * int128(int64(uint64(blockTime)) - int64(uPoint.ts)); + 681 | | if (uPoint.bias > 0) { + 682 | | balance = uint256(uint128(uPoint.bias)); + 683 | | } + 684 | | } + 685 | | + 686 | | /// @dev Calculate total voting power at some point in the past. + 687 | | /// @param lastPoint The point (bias/slope) to start the search from. + 688 | | /// @param ts Time to calculate the total voting power at. + 689 | | /// @return vSupply Total voting power at that time. + 690 | | function _supplyLockedAt(PointVoting memory lastPoint, uint64 ts) internal view returns (uint256 vSupply) { + 691 | | // The timestamp is rounded and < 2^64-1 + 692 | | uint64 tStep = (lastPoint.ts / WEEK) * WEEK; + 693 | | for (uint256 i = 0; i < 255; ++i) { + 694 | | // This is always practically < 2^64-1 + 695 | | unchecked { + 696 | | tStep += WEEK; + 697 | | } + 698 | | int128 dSlope; + 699 | | if (tStep > ts) { + 700 | | tStep = ts; + 701 | | } else { + 702 | | dSlope = mapSlopeChanges[tStep]; + 703 | | } + 704 | | lastPoint.bias -= lastPoint.slope * int128(int64(tStep) - int64(lastPoint.ts)); + 705 | | if (tStep == ts) { + 706 | | break; + 707 | | } + 708 | | lastPoint.slope += dSlope; + 709 | | lastPoint.ts = tStep; + 710 | | } + 711 | | + 712 | | if (lastPoint.bias > 0) { + 713 | | vSupply = uint256(uint128(lastPoint.bias)); + 714 | | } + 715 | | } + 716 | | + 717 | | /// @dev Gets total token supply. + 718 | | /// @return Total token supply. + 719 | | function totalSupply() public view override returns (uint256) { + 720 | | return supply; + 721 | | } + 722 | | + 723 | | /// @dev Gets total token supply at a specific block number. + 724 | | /// @param blockNumber Block number. + 725 | | /// @return supplyAt Supply at the specified block number. + 726 | | function totalSupplyAt(uint256 blockNumber) external view returns (uint256 supplyAt) { + 727 | | // Find point with the closest block number to the provided one + 728 | | (PointVoting memory sPoint, ) = _findPointByBlock(blockNumber, address(0)); + 729 | | // If the block number at the point index is bigger than the specified block number, the balance was zero + 730 | | if (sPoint.blockNumber < (blockNumber + 1)) { + 731 | | supplyAt = uint256(sPoint.balance); + 732 | | } + 733 | | } + 734 | | + 735 | | /// @dev Calculates total voting power at time `ts`. + 736 | | /// @param ts Time to get total voting power at. + 737 | | /// @return Total voting power. + 738 | | function totalSupplyLockedAtT(uint256 ts) public view returns (uint256) { + 739 | | PointVoting memory lastPoint = mapSupplyPoints[totalNumPoints]; + 740 | | return _supplyLockedAt(lastPoint, uint64(ts)); + 741 | | } + 742 | | + 743 | | /// @dev Calculates current total voting power. + 744 | | /// @return Total voting power. + 745 | | function totalSupplyLocked() public view returns (uint256) { + 746 | | return totalSupplyLockedAtT(block.timestamp); + 747 | | } + 748 | | + 749 | | /// @dev Calculate total voting power at some point in the past. + 750 | | /// @param blockNumber Block number to calculate the total voting power at. + 751 | | /// @return Total voting power. + 752 | | function getPastTotalSupply(uint256 blockNumber) public view override returns (uint256) { + 753 | | (PointVoting memory sPoint, uint256 blockTime) = _getBlockTime(blockNumber); + 754 | | // Now dt contains info on how far are we beyond the point + 755 | | return _supplyLockedAt(sPoint, uint64(blockTime)); + 756 | | } + 757 | | + 758 | | /// @dev Gets information about the interface support. + 759 | | /// @param interfaceId A specified interface Id. + 760 | | /// @return True if this contract implements the interface defined by interfaceId. + 761 | | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + 762 | | return interfaceId == type(IERC20).interfaceId || interfaceId == type(IVotes).interfaceId || + 763 | | interfaceId == type(IERC165).interfaceId; + 764 | | } + 765 | | + 766 | | /// @dev Reverts the transfer of this token. + 767 | | function transfer(address to, uint256 amount) external virtual override returns (bool) { + 768 | | revert NonTransferable(address(this)); + 769 | | } + 770 | | + 771 | | /// @dev Reverts the approval of this token. + 772 | | function approve(address spender, uint256 amount) external virtual override returns (bool) { + 773 | | revert NonTransferable(address(this)); + 774 | | } + 775 | | + 776 | | /// @dev Reverts the transferFrom of this token. + 777 | | function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) { + 778 | | revert NonTransferable(address(this)); + 779 | | } + 780 | | + 781 | | /// @dev Reverts the allowance of this token. + 782 | | function allowance(address owner, address spender) external view virtual override returns (uint256) + 783 | | { + 784 | | revert NonTransferable(address(this)); + 785 | | } + 786 | | + 787 | | /// @dev Reverts delegates of this token. + 788 | | function delegates(address account) external view virtual override returns (address) + 789 | | { + 790 | | revert NonDelegatable(address(this)); + 791 | | } + 792 | | + 793 | | /// @dev Reverts delegate for this token. + 794 | | function delegate(address delegatee) external virtual override + 795 | | { + 796 | | revert NonDelegatable(address(this)); + 797 | | } + 798 | | + 799 | | /// @dev Reverts delegateBySig for this token. + 800 | | function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + 801 | | external virtual override + 802 | | { + 803 | | revert NonDelegatable(address(this)); + 804 | | } + 805 | | } + 806 | | + + +
+ +/home/andrey/valory/autonolas-governance/lib/solmate/src/tokens/ERC20.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | pragma solidity >=0.8.0; + 3 | | + 4 | | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. + 5 | | /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol) + 6 | | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) + 7 | | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. + 8 | | abstract contract ERC20 { + 9 | | /*////////////////////////////////////////////////////////////// + 10 | | EVENTS + 11 | | //////////////////////////////////////////////////////////////*/ + 12 | | + 13 | | event Transfer(address indexed from, address indexed to, uint256 amount); + 14 | | + 15 | | event Approval(address indexed owner, address indexed spender, uint256 amount); + 16 | | + 17 | | /*////////////////////////////////////////////////////////////// + 18 | | METADATA STORAGE + 19 | | //////////////////////////////////////////////////////////////*/ + 20 | | + 21 | | string public name; + 22 | | + 23 | | string public symbol; + 24 | | + 25 | | uint8 public immutable decimals; + 26 | | + 27 | | /*////////////////////////////////////////////////////////////// + 28 | | ERC20 STORAGE + 29 | | //////////////////////////////////////////////////////////////*/ + 30 | | + 31 | | uint256 public totalSupply; + 32 | | + 33 | * | mapping(address => uint256) public balanceOf; + 34 | | + 35 | | mapping(address => mapping(address => uint256)) public allowance; + 36 | | + 37 | | /*////////////////////////////////////////////////////////////// + 38 | | EIP-2612 STORAGE + 39 | | //////////////////////////////////////////////////////////////*/ + 40 | | + 41 | | uint256 internal immutable INITIAL_CHAIN_ID; + 42 | | + 43 | | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + 44 | | + 45 | | mapping(address => uint256) public nonces; + 46 | | + 47 | | /*////////////////////////////////////////////////////////////// + 48 | | CONSTRUCTOR + 49 | | //////////////////////////////////////////////////////////////*/ + 50 | | + 51 | | constructor( + 52 | | string memory _name, + 53 | | string memory _symbol, + 54 | | uint8 _decimals + 55 | | ) { + 56 | | name = _name; + 57 | | symbol = _symbol; + 58 | | decimals = _decimals; + 59 | | + 60 | | INITIAL_CHAIN_ID = block.chainid; + 61 | | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); + 62 | | } + 63 | | + 64 | | /*////////////////////////////////////////////////////////////// + 65 | | ERC20 LOGIC + 66 | | //////////////////////////////////////////////////////////////*/ + 67 | | + 68 | * | function approve(address spender, uint256 amount) public virtual returns (bool) { + 69 | * | allowance[msg.sender][spender] = amount; + 70 | | + 71 | * | emit Approval(msg.sender, spender, amount); + 72 | | + 73 | * | return true; + 74 | | } + 75 | | + 76 | | function transfer(address to, uint256 amount) public virtual returns (bool) { + 77 | | balanceOf[msg.sender] -= amount; + 78 | | + 79 | | // Cannot overflow because the sum of all user + 80 | | // balances can't exceed the max uint256 value. + 81 | | unchecked { + 82 | | balanceOf[to] += amount; + 83 | | } + 84 | | + 85 | | emit Transfer(msg.sender, to, amount); + 86 | | + 87 | | return true; + 88 | | } + 89 | | + 90 | * | function transferFrom( + 91 | | address from, + 92 | | address to, + 93 | | uint256 amount + 94 | * | ) public virtual returns (bool) { + 95 | * | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + 96 | | + 97 | * | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + 98 | | + 99 | * | balanceOf[from] -= amount; + 100 | | + 101 | | // Cannot overflow because the sum of all user + 102 | | // balances can't exceed the max uint256 value. + 103 | | unchecked { + 104 | * | balanceOf[to] += amount; + 105 | | } + 106 | | + 107 | * | emit Transfer(from, to, amount); + 108 | | + 109 | * | return true; + 110 | | } + 111 | | + 112 | | /*////////////////////////////////////////////////////////////// + 113 | | EIP-2612 LOGIC + 114 | | //////////////////////////////////////////////////////////////*/ + 115 | | + 116 | | function permit( + 117 | | address owner, + 118 | | address spender, + 119 | | uint256 value, + 120 | | uint256 deadline, + 121 | | uint8 v, + 122 | | bytes32 r, + 123 | | bytes32 s + 124 | | ) public virtual { + 125 | | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + 126 | | + 127 | | // Unchecked because the only math done is incrementing + 128 | | // the owner's nonce which cannot realistically overflow. + 129 | | unchecked { + 130 | | address recoveredAddress = ecrecover( + 131 | | keccak256( + 132 | | abi.encodePacked( + 133 | | "\x19\x01", + 134 | | DOMAIN_SEPARATOR(), + 135 | | keccak256( + 136 | | abi.encode( + 137 | | keccak256( + 138 | | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + 139 | | ), + 140 | | owner, + 141 | | spender, + 142 | | value, + 143 | | nonces[owner]++, + 144 | | deadline + 145 | | ) + 146 | | ) + 147 | | ) + 148 | | ), + 149 | | v, + 150 | | r, + 151 | | s + 152 | | ); + 153 | | + 154 | | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + 155 | | + 156 | | allowance[recoveredAddress][spender] = value; + 157 | | } + 158 | | + 159 | | emit Approval(owner, spender, value); + 160 | | } + 161 | | + 162 | | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { + 163 | | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + 164 | | } + 165 | | + 166 | | function computeDomainSeparator() internal view virtual returns (bytes32) { + 167 | | return + 168 | | keccak256( + 169 | | abi.encode( + 170 | | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + 171 | | keccak256(bytes(name)), + 172 | | keccak256("1"), + 173 | | block.chainid, + 174 | | address(this) + 175 | | ) + 176 | | ); + 177 | | } + 178 | | + 179 | | /*////////////////////////////////////////////////////////////// + 180 | | INTERNAL MINT/BURN LOGIC + 181 | | //////////////////////////////////////////////////////////////*/ + 182 | | + 183 | | function _mint(address to, uint256 amount) internal virtual { + 184 | | totalSupply += amount; + 185 | | + 186 | | // Cannot overflow because the sum of all user + 187 | | // balances can't exceed the max uint256 value. + 188 | | unchecked { + 189 | | balanceOf[to] += amount; + 190 | | } + 191 | | + 192 | | emit Transfer(address(0), to, amount); + 193 | | } + 194 | | + 195 | | function _burn(address from, uint256 amount) internal virtual { + 196 | | balanceOf[from] -= amount; + 197 | | + 198 | | // Cannot underflow because a user's balance + 199 | | // will never be larger than the total supply. + 200 | | unchecked { + 201 | | totalSupply -= amount; + 202 | | } + 203 | | + 204 | | emit Transfer(from, address(0), amount); + 205 | | } + 206 | | } + 207 | | + + +
+ +/home/andrey/valory/autonolas-governance/node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | // OpenZeppelin Contracts (last updated v4.5.0) (governance/utils/IVotes.sol) + 3 | | pragma solidity ^0.8.0; + 4 | | + 5 | | /** + 6 | | * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. + 7 | | * + 8 | | * _Available since v4.5._ + 9 | | */ + 10 | | interface IVotes { + 11 | | /** + 12 | | * @dev Emitted when an account changes their delegate. + 13 | | */ + 14 | | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + 15 | | + 16 | | /** + 17 | | * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. + 18 | | */ + 19 | | event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); + 20 | | + 21 | | /** + 22 | | * @dev Returns the current amount of votes that `account` has. + 23 | | */ + 24 | | function getVotes(address account) external view returns (uint256); + 25 | | + 26 | | /** + 27 | | * @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`). + 28 | | */ + 29 | | function getPastVotes(address account, uint256 blockNumber) external view returns (uint256); + 30 | | + 31 | | /** + 32 | | * @dev Returns the total supply of votes available at the end of a past block (`blockNumber`). + 33 | | * + 34 | | * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. + 35 | | * Votes that have not been delegated are still part of total supply, even though they would not participate in a + 36 | | * vote. + 37 | | */ + 38 | | function getPastTotalSupply(uint256 blockNumber) external view returns (uint256); + 39 | | + 40 | | /** + 41 | | * @dev Returns the delegate that `account` has chosen. + 42 | | */ + 43 | | function delegates(address account) external view returns (address); + 44 | | + 45 | | /** + 46 | | * @dev Delegates votes from the sender to `delegatee`. + 47 | | */ + 48 | | function delegate(address delegatee) external; + 49 | | + 50 | | /** + 51 | | * @dev Delegates votes from signer to `delegatee`. + 52 | | */ + 53 | | function delegateBySig( + 54 | | address delegatee, + 55 | | uint256 nonce, + 56 | | uint256 expiry, + 57 | | uint8 v, + 58 | | bytes32 r, + 59 | | bytes32 s + 60 | | ) external; + 61 | | } + 62 | | + + +
+ +/home/andrey/valory/autonolas-governance/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + 3 | | + 4 | | pragma solidity ^0.8.0; + 5 | | + 6 | | /** + 7 | | * @dev Interface of the ERC20 standard as defined in the EIP. + 8 | | */ + 9 | | interface IERC20 { + 10 | | /** + 11 | | * @dev Emitted when `value` tokens are moved from one account (`from`) to + 12 | | * another (`to`). + 13 | | * + 14 | | * Note that `value` may be zero. + 15 | | */ + 16 | | event Transfer(address indexed from, address indexed to, uint256 value); + 17 | | + 18 | | /** + 19 | | * @dev Emitted when the allowance of a `spender` for an `owner` is set by + 20 | | * a call to {approve}. `value` is the new allowance. + 21 | | */ + 22 | | event Approval(address indexed owner, address indexed spender, uint256 value); + 23 | | + 24 | | /** + 25 | | * @dev Returns the amount of tokens in existence. + 26 | | */ + 27 | | function totalSupply() external view returns (uint256); + 28 | | + 29 | | /** + 30 | | * @dev Returns the amount of tokens owned by `account`. + 31 | | */ + 32 | | function balanceOf(address account) external view returns (uint256); + 33 | | + 34 | | /** + 35 | | * @dev Moves `amount` tokens from the caller's account to `to`. + 36 | | * + 37 | | * Returns a boolean value indicating whether the operation succeeded. + 38 | | * + 39 | | * Emits a {Transfer} event. + 40 | | */ + 41 | | function transfer(address to, uint256 amount) external returns (bool); + 42 | | + 43 | | /** + 44 | | * @dev Returns the remaining number of tokens that `spender` will be + 45 | | * allowed to spend on behalf of `owner` through {transferFrom}. This is + 46 | | * zero by default. + 47 | | * + 48 | | * This value changes when {approve} or {transferFrom} are called. + 49 | | */ + 50 | | function allowance(address owner, address spender) external view returns (uint256); + 51 | | + 52 | | /** + 53 | | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + 54 | | * + 55 | | * Returns a boolean value indicating whether the operation succeeded. + 56 | | * + 57 | | * IMPORTANT: Beware that changing an allowance with this method brings the risk + 58 | | * that someone may use both the old and the new allowance by unfortunate + 59 | | * transaction ordering. One possible solution to mitigate this race + 60 | | * condition is to first reduce the spender's allowance to 0 and set the + 61 | | * desired value afterwards: + 62 | | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + 63 | | * + 64 | | * Emits an {Approval} event. + 65 | | */ + 66 | | function approve(address spender, uint256 amount) external returns (bool); + 67 | | + 68 | | /** + 69 | | * @dev Moves `amount` tokens from `from` to `to` using the + 70 | | * allowance mechanism. `amount` is then deducted from the caller's + 71 | | * allowance. + 72 | | * + 73 | | * Returns a boolean value indicating whether the operation succeeded. + 74 | | * + 75 | | * Emits a {Transfer} event. + 76 | | */ + 77 | | function transferFrom( + 78 | | address from, + 79 | | address to, + 80 | | uint256 amount + 81 | | ) external returns (bool); + 82 | | } + 83 | | + + +
+ +/home/andrey/valory/autonolas-governance/node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol + + 1 | | // SPDX-License-Identifier: MIT + 2 | | // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + 3 | | + 4 | | pragma solidity ^0.8.0; + 5 | | + 6 | | /** + 7 | | * @dev Interface of the ERC165 standard, as defined in the + 8 | | * https://eips.ethereum.org/EIPS/eip-165[EIP]. + 9 | | * + 10 | | * Implementers can declare support of contract interfaces, which can then be + 11 | | * queried by others ({ERC165Checker}). + 12 | | * + 13 | | * For an implementation, see {ERC165}. + 14 | | */ + 15 | | interface IERC165 { + 16 | | /** + 17 | | * @dev Returns true if this contract implements the interface defined by + 18 | | * `interfaceId`. See the corresponding + 19 | | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + 20 | | * to learn more about how these ids are created. + 21 | | * + 22 | | * This function call must use less than 30 000 gas. + 23 | | */ + 24 | | function supportsInterface(bytes4 interfaceId) external view returns (bool); + 25 | | } + 26 | | + + +
+ diff --git a/audits/internal12/analysis/fuzzing/overflow/echidna_overflow.yaml b/audits/internal12/analysis/fuzzing/overflow/echidna_overflow.yaml new file mode 100644 index 0000000..a8ae0b6 --- /dev/null +++ b/audits/internal12/analysis/fuzzing/overflow/echidna_overflow.yaml @@ -0,0 +1,10 @@ +testMode: overflow +#testMode: assertion +coverage: true +corpusDir: corpusEchidna +coverageFormats: ["html"] +# maxBlockDelay: 12 +# provide solc remappings to crytic-compile +# https://www.justinsilver.com/technology/programming/slither-echidna-remappings/ +cryticArgs: ['--solc-remaps', '@=node_modules/@'] + diff --git a/audits/internal12/analysis/fuzzing/overflow/fuzzing-overflow.PNG b/audits/internal12/analysis/fuzzing/overflow/fuzzing-overflow.PNG new file mode 100755 index 0000000..cf720e0 Binary files /dev/null and b/audits/internal12/analysis/fuzzing/overflow/fuzzing-overflow.PNG differ diff --git a/audits/internal12/analysis/fuzzing/overflow/start_echidna.sh b/audits/internal12/analysis/fuzzing/overflow/start_echidna.sh new file mode 100644 index 0000000..7acd496 --- /dev/null +++ b/audits/internal12/analysis/fuzzing/overflow/start_echidna.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -rf corpusEchidna/ +echidna contracts/test/EchidnaVoteWeightingAssert.sol --contract EchidnaVoteWeightingAssert --config echidna_overflow.yaml diff --git a/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IDispenser.call-graph.png b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IDispenser.call-graph.png new file mode 100644 index 0000000..68ad698 Binary files /dev/null and b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IDispenser.call-graph.png differ diff --git a/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IVEOLAS.call-graph.png b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IVEOLAS.call-graph.png new file mode 100644 index 0000000..789a38f Binary files /dev/null and b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.IVEOLAS.call-graph.png differ diff --git a/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.VoteWeighting.call-graph.png b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.VoteWeighting.call-graph.png new file mode 100644 index 0000000..986cece Binary files /dev/null and b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.VoteWeighting.call-graph.png differ diff --git a/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.all_contracts.call-graph.png b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.all_contracts.call-graph.png new file mode 100644 index 0000000..b709dea Binary files /dev/null and b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.all_contracts.call-graph.png differ diff --git a/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.inheritance-graph.png b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.inheritance-graph.png new file mode 100644 index 0000000..7317041 Binary files /dev/null and b/audits/internal12/analysis/slither_VoteWeighting-flatten.sol.inheritance-graph.png differ diff --git a/audits/internal12/analysis/slither_call-graph.txt b/audits/internal12/analysis/slither_call-graph.txt new file mode 100644 index 0000000..722a2f5 --- /dev/null +++ b/audits/internal12/analysis/slither_call-graph.txt @@ -0,0 +1,8 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers:Call Graph: ./VoteWeighting-flatten.sol.all_contracts.call-graph.dot +Call Graph: ./VoteWeighting-flatten.sol.IDispenser.call-graph.dot +Call Graph: ./VoteWeighting-flatten.sol.IVEOLAS.call-graph.dot +Call Graph: ./VoteWeighting-flatten.sol.VoteWeighting.call-graph.dot + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_constructor-calls.txt b/audits/internal12/analysis/slither_constructor-calls.txt new file mode 100644 index 0000000..2a93f60 --- /dev/null +++ b/audits/internal12/analysis/slither_constructor-calls.txt @@ -0,0 +1,28 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +############################# +####### VoteWeighting ####### +############################# + +## Constructor Call Sequence + - VoteWeighting + +## Constructor Definitions + +### VoteWeighting + + constructor(address _ve) { + // Check for the zero address + if (_ve == address(0)) { + revert ZeroAddress(); + } + + // Set initial parameters + owner = msg.sender; + ve = _ve; + timeSum = block.timestamp / WEEK * WEEK; + setNominees.push(Nominee(bytes32(0), 0)); + } + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_contract-summary.txt b/audits/internal12/analysis/slither_contract-summary.txt new file mode 100644 index 0000000..8096d87 --- /dev/null +++ b/audits/internal12/analysis/slither_contract-summary.txt @@ -0,0 +1,43 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: ++ Contract IDispenser (Most derived contract) + - From IDispenser + - addNominee(bytes32) (external) + - removeNominee(bytes32) (external) + ++ Contract IVEOLAS (Most derived contract) + - From IVEOLAS + - getLastUserPoint(address) (external) + - lockedEnd(address) (external) + ++ Contract VoteWeighting (Most derived contract) + - From VoteWeighting + - _addNominee(Nominee) (internal) + - _getSum() (internal) + - _getWeight(bytes32,uint256) (internal) + - _maxAndSub(uint256,uint256) (internal) + - _nomineeRelativeWeight(bytes32,uint256,uint256) (internal) + - addNomineeEVM(address,uint256) (external) + - addNomineeNonEVM(bytes32,uint256) (external) + - changeDispenser(address) (external) + - changeOwner(address) (external) + - checkpoint() (external) + - checkpointNominee(bytes32,uint256) (external) + - constructor(address) (public) + - getAllNominees() (external) + - getNextAllowedVotingTimes(bytes32[],uint256[],address[]) (external) + - getNominee(uint256) (external) + - getNomineeId(bytes32,uint256) (external) + - getNomineeWeight(bytes32,uint256) (external) + - getNominees(uint256,uint256) (external) + - getNumNominees() (external) + - getWeightsSum() (external) + - nomineeRelativeWeight(bytes32,uint256,uint256) (external) + - nomineeRelativeWeightWrite(bytes32,uint256,uint256) (external) + - removeNominee(bytes32,uint256) (external) + - retrieveRemovedNomineeVotingPower(bytes32,uint256) (external) + - voteForNomineeWeights(bytes32,uint256,uint256) (public) + - voteForNomineeWeightsBatch(bytes32[],uint256[],uint256[]) (external) + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_data-dependency.txt b/audits/internal12/analysis/slither_data-dependency.txt new file mode 100644 index 0000000..bf098c2 --- /dev/null +++ b/audits/internal12/analysis/slither_data-dependency.txt @@ -0,0 +1,890 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Contract IDispenser ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ ++----------+--------------+ + +Function addNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +Function removeNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +INFO:Printers: +Contract IDispenser ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ ++----------+--------------+ + +Function addNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +Function removeNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +Contract IVEOLAS ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ ++----------+--------------+ + +Function lockedEnd(address) ++------------+--------------+ +| Variable | Dependencies | ++------------+--------------+ +| account | [] | +| unlockTime | [] | ++------------+--------------+ +Function getLastUserPoint(address) ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ +| account | [] | +| pv | [] | ++----------+--------------+ +INFO:Printers: +Contract IDispenser ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ ++----------+--------------+ + +Function addNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +Function removeNominee(bytes32) ++-------------+--------------+ +| Variable | Dependencies | ++-------------+--------------+ +| nomineeHash | [] | ++-------------+--------------+ +Contract IVEOLAS ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ ++----------+--------------+ + +Function lockedEnd(address) ++------------+--------------+ +| Variable | Dependencies | ++------------+--------------+ +| account | [] | +| unlockTime | [] | ++------------+--------------+ +Function getLastUserPoint(address) ++----------+--------------+ +| Variable | Dependencies | ++----------+--------------+ +| account | [] | +| pv | [] | ++----------+--------------+ +Contract VoteWeighting ++--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Variable | Dependencies | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| WEEK | ['WEEK'] | +| WEIGHT_VOTE_DELAY | ['WEIGHT_VOTE_DELAY'] | +| MAX_WEIGHT | ['MAX_WEIGHT'] | +| MAX_EVM_CHAIN_ID | ['MAX_EVM_CHAIN_ID'] | +| ve | ['_ve', 've'] | +| owner | ['msg.sender', 'newOwner', 'owner'] | +| dispenser | ['dispenser', 'newDispenser'] | +| setNominees | ['account', 'chainId', 'nominee', 'setNominees'] | +| mapNomineeIds | ['account', 'chainId', 'id', 'mapNomineeIds', 'nominee', 'setNominees'] | +| mapRemovedNominees | ['mapRemovedNominees'] | +| voteUserSlopes | ['voteUserSlopes'] | +| voteUserPower | ['MAX_WEIGHT', '_ve', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'powerUsed', 'slope', 've', 'voteUserPower', 'voteUserSlopes', 'weight', 'weights'] | +| lastUserVote | ['lastUserVote'] | +| pointsWeight | ['pointsWeight'] | +| changesWeight | ['changesWeight'] | +| timeWeight | ['WEEK', 'block.timestamp', 'nextTime', 't', 'timeWeight'] | +| pointsSum | ['MAX_WEIGHT', 'WEEK', '_ve', 'changesSum', 'dBias', 'dSlope', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'pointsSum', 'pt', 'slope', 've', 'voteUserSlopes', 'weight', 'weights'] | +| changesSum | ['MAX_WEIGHT', '_ve', 'changesSum', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'slope', 've', 'voteUserSlopes', 'weight', 'weights'] | +| timeSum | ['WEEK', 'block.timestamp', 'nextTime', 't', 'timeSum'] | ++--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +Function constructor(address) ++----------------------------------+-----------------------------+ +| Variable | Dependencies | ++----------------------------------+-----------------------------+ +| _ve | [] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | ['_ve'] | +| VoteWeighting.owner | ['msg.sender'] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | ['setNominees'] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | ['WEEK', 'block.timestamp'] | ++----------------------------------+-----------------------------+ +Function _getSum() ++----------------------------------+--------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+--------------------------------------------------------------+ +| | [] | +| t | ['WEEK', 't', 'timeSum'] | +| pt | ['WEEK', 'changesSum', 'dBias', 'dSlope', 'pointsSum', 'pt'] | +| i | ['i'] | +| dBias | ['WEEK', 'changesSum', 'dSlope', 'pointsSum', 'pt'] | +| dSlope | ['changesSum'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | ['WEEK', 'changesSum', 'dBias', 'dSlope', 'pointsSum', 'pt'] | +| VoteWeighting.changesSum | ['changesSum'] | +| VoteWeighting.timeSum | ['WEEK', 't', 'timeSum'] | ++----------------------------------+--------------------------------------------------------------+ +Function _getWeight(bytes32,uint256) ++----------------------------------+--------------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+--------------------------------------------------------------------+ +| account | ['account'] | +| chainId | ['chainId'] | +| | [] | +| nominee | ['account', 'chainId'] | +| nomineeHash | ['account', 'chainId', 'nominee'] | +| t | ['WEEK', 't', 'timeWeight'] | +| pt | ['WEEK', 'changesWeight', 'dBias', 'dSlope', 'pointsWeight', 'pt'] | +| i | ['i'] | +| dBias | ['WEEK', 'pointsWeight', 'pt'] | +| dSlope | ['changesWeight'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | ['mapNomineeIds'] | +| VoteWeighting.mapRemovedNominees | ['mapRemovedNominees'] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | ['pointsWeight'] | +| VoteWeighting.changesWeight | ['changesWeight'] | +| VoteWeighting.timeWeight | ['WEEK', 't', 'timeWeight'] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------------------------------------------------------------+ +Function _addNominee(Nominee) ++----------------------------------+-------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+-------------------------------------------------------+ +| nominee | ['nominee'] | +| nomineeHash | ['nominee'] | +| id | ['nominee', 'setNominees'] | +| nextTime | ['WEEK', 'block.timestamp'] | +| localDispenser | ['dispenser'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | ['dispenser'] | +| VoteWeighting.setNominees | ['nominee', 'setNominees'] | +| VoteWeighting.mapNomineeIds | ['id', 'mapNomineeIds', 'nominee', 'setNominees'] | +| VoteWeighting.mapRemovedNominees | ['mapRemovedNominees'] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | ['WEEK', 'block.timestamp', 'nextTime', 'timeWeight'] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-------------------------------------------------------+ +Function addNomineeEVM(address,uint256) ++----------------------------------+------------------------+ +| Variable | Dependencies | ++----------------------------------+------------------------+ +| account | [] | +| chainId | [] | +| nominee | ['account', 'chainId'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | ['MAX_EVM_CHAIN_ID'] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+------------------------+ +Function addNomineeNonEVM(bytes32,uint256) ++----------------------------------+------------------------+ +| Variable | Dependencies | ++----------------------------------+------------------------+ +| account | [] | +| chainId | [] | +| nominee | ['account', 'chainId'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | ['MAX_EVM_CHAIN_ID'] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+------------------------+ +Function changeOwner(address) ++----------------------------------+-----------------------+ +| Variable | Dependencies | ++----------------------------------+-----------------------+ +| newOwner | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | ['newOwner', 'owner'] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------------+ +Function changeDispenser(address) ++----------------------------------+------------------+ +| Variable | Dependencies | ++----------------------------------+------------------+ +| newDispenser | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | ['owner'] | +| VoteWeighting.dispenser | ['newDispenser'] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+------------------+ +Function checkpoint() ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function checkpointNominee(bytes32,uint256) ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| account | [] | +| chainId | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function _nomineeRelativeWeight(bytes32,uint256,uint256) ++----------------------------------+------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+------------------------------------------------------------+ +| account | ['account'] | +| chainId | ['chainId'] | +| time | ['time'] | +| weight | ['nomineeWeight', 'pointsSum', 'pointsWeight', 'totalSum'] | +| totalSum | ['pointsSum'] | +| t | ['WEEK', 'time'] | +| nominee | ['account', 'chainId'] | +| nomineeHash | ['account', 'chainId', 'nominee'] | +| nomineeWeight | ['pointsWeight'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | ['pointsWeight'] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | ['pointsSum'] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+------------------------------------------------------------+ +Function nomineeRelativeWeight(bytes32,uint256,uint256) ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| account | [] | +| chainId | [] | +| time | [] | +| weight | ['TUPLE_0'] | +| totalSum | ['TUPLE_0'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function nomineeRelativeWeightWrite(bytes32,uint256,uint256) ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| account | [] | +| chainId | [] | +| time | [] | +| weight | ['TUPLE_1'] | +| totalSum | ['TUPLE_1'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function voteForNomineeWeights(bytes32,uint256,uint256) ++----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+ +| account | [] | +| chainId | [] | +| weight | [] | +| nomineeHash | ['account', 'chainId'] | +| slope | ['msg.sender', 've'] | +| lockEnd | ['msg.sender', 've'] | +| nextTime | ['WEEK', 'block.timestamp'] | +| nextAllowedVotingTime | ['WEIGHT_VOTE_DELAY', 'lastUserVote'] | +| oldSlope | ['oldSlope', 'voteUserSlopes'] | +| oldBias | ['WEEK', 'block.timestamp', 'nextTime', 'oldBias', 'oldSlope', 'voteUserSlopes'] | +| newSlope | ['MAX_WEIGHT', 'lockEnd', 'msg.sender', 'newSlope', 'slope', 've', 'weight'] | +| newBias | ['MAX_WEIGHT', 'WEEK', 'block.timestamp', 'lockEnd', 'msg.sender', 'newSlope', 'nextTime', 'slope', 've', 'weight'] | +| powerUsed | ['MAX_WEIGHT', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'powerUsed', 'slope', 've', 'voteUserPower', 'voteUserSlopes', 'weight'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | ['WEIGHT_VOTE_DELAY'] | +| VoteWeighting.MAX_WEIGHT | ['MAX_WEIGHT'] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | ['ve'] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | ['mapRemovedNominees'] | +| VoteWeighting.voteUserSlopes | ['voteUserSlopes'] | +| VoteWeighting.voteUserPower | ['MAX_WEIGHT', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'powerUsed', 'slope', 've', 'voteUserPower', 'voteUserSlopes', 'weight'] | +| VoteWeighting.lastUserVote | ['lastUserVote'] | +| VoteWeighting.pointsWeight | ['pointsWeight'] | +| VoteWeighting.changesWeight | ['changesWeight'] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | ['pointsSum'] | +| VoteWeighting.changesSum | ['MAX_WEIGHT', 'changesSum', 'lockEnd', 'msg.sender', 'newSlope', 'oldSlope', 'slope', 've', 'voteUserSlopes', 'weight'] | +| VoteWeighting.timeSum | [] | ++----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+ +Function voteForNomineeWeightsBatch(bytes32[],uint256[],uint256[]) ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| accounts | ['accounts'] | +| chainIds | ['chainIds'] | +| weights | ['weights'] | +| i | ['i'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function _maxAndSub(uint256,uint256) ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| a | [] | +| b | ['oldBias'] | +| | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +Function removeNominee(bytes32,uint256) ++----------------------------------+-------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+-------------------------------------------------------+ +| account | [] | +| chainId | [] | +| nominee | ['account', 'chainId', 'setNominees'] | +| nomineeHash | ['account', 'chainId', 'nominee', 'setNominees'] | +| id | ['mapNomineeIds'] | +| oldWeight | [] | +| oldSum | [] | +| nextTime | ['WEEK', 'block.timestamp'] | +| newSum | ['oldSum', 'oldWeight'] | +| localDispenser | ['dispenser'] | +| VoteWeighting.WEEK | ['WEEK'] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | ['owner'] | +| VoteWeighting.dispenser | ['dispenser'] | +| VoteWeighting.setNominees | ['nominee', 'setNominees'] | +| VoteWeighting.mapNomineeIds | ['id', 'mapNomineeIds'] | +| VoteWeighting.mapRemovedNominees | ['mapRemovedNominees'] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | ['pointsWeight'] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | ['WEEK', 'block.timestamp', 'nextTime', 'timeWeight'] | +| VoteWeighting.pointsSum | ['pointsSum'] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | ['WEEK', 'block.timestamp', 'nextTime'] | ++----------------------------------+-------------------------------------------------------+ +Function retrieveRemovedNomineeVotingPower(bytes32,uint256) ++----------------------------------+--------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+--------------------------------------------------------------+ +| account | [] | +| chainId | [] | +| nominee | ['account', 'chainId'] | +| nomineeHash | ['account', 'chainId', 'nominee'] | +| oldSlope | ['oldSlope', 'voteUserSlopes'] | +| powerUsed | ['oldSlope', 'powerUsed', 'voteUserPower', 'voteUserSlopes'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | ['mapRemovedNominees'] | +| VoteWeighting.voteUserSlopes | ['voteUserSlopes'] | +| VoteWeighting.voteUserPower | ['oldSlope', 'powerUsed', 'voteUserPower', 'voteUserSlopes'] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | ['changesWeight'] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | ['changesSum', 'oldSlope', 'voteUserSlopes'] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------------------------------------------------------+ +Function getNomineeWeight(bytes32,uint256) ++----------------------------------+-----------------------------------+ +| Variable | Dependencies | ++----------------------------------+-----------------------------------+ +| account | [] | +| chainId | [] | +| | [] | +| nominee | ['account', 'chainId'] | +| nomineeHash | ['account', 'chainId', 'nominee'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | ['pointsWeight'] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | ['timeWeight'] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------------------------+ +Function getWeightsSum() ++----------------------------------+---------------+ +| Variable | Dependencies | ++----------------------------------+---------------+ +| | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | ['pointsSum'] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | ['timeSum'] | ++----------------------------------+---------------+ +Function getNumNominees() ++----------------------------------+-----------------+ +| Variable | Dependencies | ++----------------------------------+-----------------+ +| | [] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | ['setNominees'] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------+ +Function getAllNominees() ++----------------------------------+-----------------+ +| Variable | Dependencies | ++----------------------------------+-----------------+ +| nominees | ['setNominees'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | ['setNominees'] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------+ +Function getNomineeId(bytes32,uint256) ++----------------------------------+-----------------------------------+ +| Variable | Dependencies | ++----------------------------------+-----------------------------------+ +| account | [] | +| chainId | [] | +| id | ['mapNomineeIds'] | +| nominee | ['account', 'chainId'] | +| nomineeHash | ['account', 'chainId', 'nominee'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | ['mapNomineeIds'] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------------------------+ +Function getNominee(uint256) ++----------------------------------+-----------------+ +| Variable | Dependencies | ++----------------------------------+-----------------+ +| id | [] | +| nominee | ['setNominees'] | +| totalNumNominees | ['setNominees'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | ['setNominees'] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------+ +Function getNominees(uint256,uint256) ++----------------------------------+--------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+--------------------------------------------+ +| startId | [] | +| numNominees | [] | +| nominees | ['nominees', 'numNominees', 'setNominees'] | +| endId | ['numNominees', 'startId'] | +| totalNumNominees | ['setNominees'] | +| i | ['i'] | +| id | ['i', 'startId'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | ['setNominees'] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------------------------------------+ +Function getNextAllowedVotingTimes(bytes32[],uint256[],address[]) ++----------------------------------+-----------------------------------------------------------------------------+ +| Variable | Dependencies | ++----------------------------------+-----------------------------------------------------------------------------+ +| accounts | ['accounts'] | +| chainIds | ['chainIds'] | +| voters | ['voters'] | +| nextAllowedVotingTimes | ['WEIGHT_VOTE_DELAY', 'accounts', 'lastUserVote', 'nextAllowedVotingTimes'] | +| i | ['i'] | +| nominee | ['accounts', 'chainIds'] | +| nomineeHash | ['accounts', 'chainIds', 'nominee'] | +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | ['WEIGHT_VOTE_DELAY'] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | ['mapNomineeIds'] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | ['lastUserVote'] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+-----------------------------------------------------------------------------+ +Function slitherConstructorConstantVariables() ++----------------------------------+--------------+ +| Variable | Dependencies | ++----------------------------------+--------------+ +| VoteWeighting.WEEK | [] | +| VoteWeighting.WEIGHT_VOTE_DELAY | [] | +| VoteWeighting.MAX_WEIGHT | [] | +| VoteWeighting.MAX_EVM_CHAIN_ID | [] | +| VoteWeighting.ve | [] | +| VoteWeighting.owner | [] | +| VoteWeighting.dispenser | [] | +| VoteWeighting.setNominees | [] | +| VoteWeighting.mapNomineeIds | [] | +| VoteWeighting.mapRemovedNominees | [] | +| VoteWeighting.voteUserSlopes | [] | +| VoteWeighting.voteUserPower | [] | +| VoteWeighting.lastUserVote | [] | +| VoteWeighting.pointsWeight | [] | +| VoteWeighting.changesWeight | [] | +| VoteWeighting.timeWeight | [] | +| VoteWeighting.pointsSum | [] | +| VoteWeighting.changesSum | [] | +| VoteWeighting.timeSum | [] | ++----------------------------------+--------------+ +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_full.txt b/audits/internal12/analysis/slither_full.txt new file mode 100644 index 0000000..c7f482d --- /dev/null +++ b/audits/internal12/analysis/slither_full.txt @@ -0,0 +1,257 @@ +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getAllNominees() (VoteWeighting-flatten.sol#668-670): + State variables read that were written after the external call(s): + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at nominees = setNominees (VoteWeighting-flatten.sol#669) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getNextAllowedVotingTimes(bytes32[],uint256[],address[]) (VoteWeighting-flatten.sol#742-769): + State variables read that were written after the external call(s): + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at mapNomineeIds[nomineeHash] == 0 (VoteWeighting-flatten.sol#762) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at revert NomineeDoesNotExist(bytes32,uint256)(accounts[i],chainIds[i]) (VoteWeighting-flatten.sol#763) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at END_IF (VoteWeighting-flatten.sol#762-764) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at nextAllowedVotingTimes[i] = lastUserVote[voters[i]][nomineeHash] + WEIGHT_VOTE_DELAY (VoteWeighting-flatten.sol#767) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at ++ i (VoteWeighting-flatten.sol#756) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getNominee(uint256) (VoteWeighting-flatten.sol#688-699): + State variables read that were written after the external call(s): + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at totalNumNominees = setNominees.length - 1 (VoteWeighting-flatten.sol#690) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at id == 0 (VoteWeighting-flatten.sol#692) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at revert ZeroValue()() (VoteWeighting-flatten.sol#693) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at id > totalNumNominees (VoteWeighting-flatten.sol#694) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at revert Overflow(uint256,uint256)(id,totalNumNominees) (VoteWeighting-flatten.sol#695) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at END_IF (VoteWeighting-flatten.sol#694-696) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at END_IF (VoteWeighting-flatten.sol#692-696) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at nominee = setNominees[id] (VoteWeighting-flatten.sol#698) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getNomineeId(bytes32,uint256) (VoteWeighting-flatten.sol#676-682): + State variables read that were written after the external call(s): + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) was read at id = mapNomineeIds[nomineeHash] (VoteWeighting-flatten.sol#681) + This variable was written at (after external call): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getNominees(uint256,uint256) (VoteWeighting-flatten.sol#706-735): + State variables read that were written after the external call(s): + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at totalNumNominees = setNominees.length (VoteWeighting-flatten.sol#719) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at endId > totalNumNominees (VoteWeighting-flatten.sol#722) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at revert Overflow(uint256,uint256)(endId,totalNumNominees) (VoteWeighting-flatten.sol#723) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at END_IF (VoteWeighting-flatten.sol#722-724) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at nominees = new Nominee[](numNominees) (VoteWeighting-flatten.sol#727) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at BEGIN_LOOP (VoteWeighting-flatten.sol#730-734) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at END_LOOP (VoteWeighting-flatten.sol#730-734) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at i = 0 (VoteWeighting-flatten.sol#730) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at i < numNominees (VoteWeighting-flatten.sol#730) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at id = i + startId (VoteWeighting-flatten.sol#731) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at nominees[i] = setNominees[id] (VoteWeighting-flatten.sol#733) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at ++ i (VoteWeighting-flatten.sol#730) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) + +Not issue: removeNominee owner only +Potential vulnerable to readonly-reentrancy function (if read in other function) VoteWeighting.getNumNominees() (VoteWeighting-flatten.sol#661-663): + State variables read that were written after the external call(s): + - VoteWeighting.setNominees (VoteWeighting-flatten.sol#154) was read at setNominees.length - 1 (VoteWeighting-flatten.sol#662) + This variable was written at (after external call): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) +Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/readonly_reentrancy.md + +Not issue: rounding by design +VoteWeighting.constructor(address) (VoteWeighting-flatten.sol#189-200) performs a multiplication on the result of a division: + - timeSum = block.timestamp / WEEK * WEEK (VoteWeighting-flatten.sol#198) +VoteWeighting._addNominee(Nominee) (VoteWeighting-flatten.sol#273-300) performs a multiplication on the result of a division: + - nextTime = (block.timestamp + WEEK) / WEEK * WEEK (VoteWeighting-flatten.sol#290) +VoteWeighting._nomineeRelativeWeight(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#396-411) performs a multiplication on the result of a division: + - t = time / WEEK * WEEK (VoteWeighting-flatten.sol#401) +VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) performs a multiplication on the result of a division: + - nextTime = (block.timestamp + WEEK) / WEEK * WEEK (VoteWeighting-flatten.sol#462) +VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606) performs a multiplication on the result of a division: + - nextTime = (block.timestamp + WEEK) / WEEK * WEEK (VoteWeighting-flatten.sol#577) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply + +Not issue, but better fix +Reentrancy in VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606): + External calls: + - IDispenser(localDispenser).removeNominee(nomineeHash) (VoteWeighting-flatten.sol#592) + State variables written after the call(s): + - mapNomineeIds[nomineeHash] = 0 (VoteWeighting-flatten.sol#596) + VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) can be used in cross function reentrancies: + - VoteWeighting._addNominee(Nominee) (VoteWeighting-flatten.sol#273-300) + - VoteWeighting._getWeight(bytes32,uint256) (VoteWeighting-flatten.sol#235-269) + - VoteWeighting.getNextAllowedVotingTimes(bytes32[],uint256[],address[]) (VoteWeighting-flatten.sol#742-769) + - VoteWeighting.getNomineeId(bytes32,uint256) (VoteWeighting-flatten.sol#676-682) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) + - VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606) + - mapNomineeIds[nomineeHash] = id (VoteWeighting-flatten.sol#600) + VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) can be used in cross function reentrancies: + - VoteWeighting._addNominee(Nominee) (VoteWeighting-flatten.sol#273-300) + - VoteWeighting._getWeight(bytes32,uint256) (VoteWeighting-flatten.sol#235-269) + - VoteWeighting.getNextAllowedVotingTimes(bytes32[],uint256[],address[]) (VoteWeighting-flatten.sol#742-769) + - VoteWeighting.getNomineeId(bytes32,uint256) (VoteWeighting-flatten.sol#676-682) + - VoteWeighting.mapNomineeIds (VoteWeighting-flatten.sol#156) + - VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1 + +No issue. +VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256).oldBias (VoteWeighting-flatten.sol#482) is a local variable never initialized +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables + +Pay attention. +Dubious typecast in VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530): + int128 => uint128 casting occurs in slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)) (VoteWeighting-flatten.sol#460) +Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/dubious_typecast.md +original: +interface VotingEscrow: + def get_last_user_slope(addr: address) -> int128: view +slope: uint256 = convert(VotingEscrow(escrow).get_last_user_slope(msg.sender), uint256) +own code: uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); +https://vyper.readthedocs.io/_/downloads/en/stable/pdf/ page 50 +• Converting between signed and unsigned integers reverts if the input is negative + +Pay attention. +VoteWeighting.changeDispenser(address).newDispenser (VoteWeighting-flatten.sol#367) lacks a zero-check on : + - dispenser = newDispenser (VoteWeighting-flatten.sol#373) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation + +No issue by design. +VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) has external calls inside a loop: slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)) (VoteWeighting-flatten.sol#460) +VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) has external calls inside a loop: lockEnd = IVEOLAS(ve).lockedEnd(msg.sender) (VoteWeighting-flatten.sol#461) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop + +Fix no CEI. +Reentrancy in VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606): + External calls: + - IDispenser(localDispenser).removeNominee(nomineeHash) (VoteWeighting-flatten.sol#592) + State variables written after the call(s): + - setNominees[id] = nominee (VoteWeighting-flatten.sol#601) + - setNominees.pop() (VoteWeighting-flatten.sol#603) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2 + +Not issue. dispenser is trusted +Reentrancy in VoteWeighting._addNominee(Nominee) (VoteWeighting-flatten.sol#273-300): + External calls: + - IDispenser(localDispenser).addNominee(nomineeHash) (VoteWeighting-flatten.sol#296) + Event emitted after the call(s): + - AddNominee(nominee.account,nominee.chainId,id) (VoteWeighting-flatten.sol#299) +Reentrancy in VoteWeighting.removeNominee(bytes32,uint256) (VoteWeighting-flatten.sol#558-606): + External calls: + - IDispenser(localDispenser).removeNominee(nomineeHash) (VoteWeighting-flatten.sol#592) + Event emitted after the call(s): + - RemoveNominee(account,chainId,newSum) (VoteWeighting-flatten.sol#605) + +Not issue. By design +VoteWeighting._getSum() (VoteWeighting-flatten.sol#204-229) uses timestamp for comparisons + Dangerous comparisons: + - t > block.timestamp (VoteWeighting-flatten.sol#209) + - t > block.timestamp (VoteWeighting-flatten.sol#224) +VoteWeighting._getWeight(bytes32,uint256) (VoteWeighting-flatten.sol#235-269) uses timestamp for comparisons + Dangerous comparisons: + - t > block.timestamp (VoteWeighting-flatten.sol#249) + - t > block.timestamp (VoteWeighting-flatten.sol#264) +VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) uses timestamp for comparisons + Dangerous comparisons: + - nextTime >= lockEnd (VoteWeighting-flatten.sol#465) + - nextAllowedVotingTime > block.timestamp (VoteWeighting-flatten.sol#476) + - oldSlope.end > nextTime (VoteWeighting-flatten.sol#483) + - oldSlope.end > nextTime (VoteWeighting-flatten.sol#507) + - oldSlope.end > block.timestamp (VoteWeighting-flatten.sol#515) +VoteWeighting._maxAndSub(uint256,uint256) (VoteWeighting-flatten.sol#551-553) uses timestamp for comparisons + Dangerous comparisons: + - a > b (VoteWeighting-flatten.sol#552) +VoteWeighting.retrieveRemovedNomineeVotingPower(bytes32,uint256) (VoteWeighting-flatten.sol#611-638) uses timestamp for comparisons + Dangerous comparisons: + - oldSlope.end > block.timestamp (VoteWeighting-flatten.sol#628) + +Not issue. By design +VoteWeighting._getSum() (VoteWeighting-flatten.sol#204-229) has costly operations inside a loop: + - timeSum = t (VoteWeighting-flatten.sol#225) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop + + +Not issue. Constant. +In a function VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) variable VoteWeighting.MAX_WEIGHT (VoteWeighting-flatten.sol#143) is read multiple times + +Not issue. Optimization is not needed due to loss of code readability. +In a function VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) variable VoteWeighting.pointsSum (VoteWeighting-flatten.sol#181) is read multiple times +In a function VoteWeighting.voteForNomineeWeights(bytes32,uint256,uint256) (VoteWeighting-flatten.sol#451-530) variable VoteWeighting.pointsWeight (VoteWeighting-flatten.sol#174) is read multiple times +Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/multiple_storage_read.md +INFO:Slither:. analyzed (3 contracts with 108 detectors), 33 result(s) found diff --git a/audits/internal12/analysis/slither_function-summary.txt b/audits/internal12/analysis/slither_function-summary.txt new file mode 100644 index 0000000..0631243 --- /dev/null +++ b/audits/internal12/analysis/slither_function-summary.txt @@ -0,0 +1,106 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Contract IDispenser +Contract vars: [] +Inheritance:: [] + ++------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ +| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ +| addNominee(bytes32) | external | [] | [] | [] | [] | [] | 2 | +| removeNominee(bytes32) | external | [] | [] | [] | [] | [] | 2 | ++------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ + ++-----------+------------+------+-------+----------------+----------------+-----------------------+ +| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++-----------+------------+------+-------+----------------+----------------+-----------------------+ ++-----------+------------+------+-------+----------------+----------------+-----------------------+ + +INFO:Printers: +Contract IVEOLAS +Contract vars: [] +Inheritance:: [] + ++---------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ +| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++---------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ +| lockedEnd(address) | external | [] | [] | [] | [] | [] | 2 | +| getLastUserPoint(address) | external | [] | [] | [] | [] | [] | 2 | ++---------------------------+------------+-----------+------+-------+----------------+----------------+-----------------------+ + ++-----------+------------+------+-------+----------------+----------------+-----------------------+ +| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++-----------+------------+------+-------+----------------+----------------+-----------------------+ ++-----------+------------+------+-------+----------------+----------------+-----------------------+ + +INFO:Printers: +Contract VoteWeighting +Contract vars: ['WEEK', 'WEIGHT_VOTE_DELAY', 'MAX_WEIGHT', 'MAX_EVM_CHAIN_ID', 've', 'owner', 'dispenser', 'setNominees', 'mapNomineeIds', 'mapRemovedNominees', 'voteUserSlopes', 'voteUserPower', 'lastUserVote', 'pointsWeight', 'changesWeight', 'timeWeight', 'pointsSum', 'changesSum', 'timeSum'] +Inheritance:: [] + ++-----------------------------------------------------------+------------+-----------+------------------------------------------+-----------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+-----------------------+ +| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++-----------------------------------------------------------+------------+-----------+------------------------------------------+-----------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+-----------------------+ +| constructor(address) | public | [] | ['WEEK', 'block.timestamp'] | ['owner', 'setNominees'] | ['revert ZeroAddress()'] | ['setNominees.push(Nominee(bytes32(0),0))'] | 2 | +| | | | ['msg.sender', 'setNominees'] | ['timeSum', 've'] | | | | +| _getSum() | internal | [] | ['WEEK', 'block.timestamp'] | ['pointsSum', 'timeSum'] | [] | [] | 5 | +| | | | ['changesSum', 'pointsSum'] | | | | | +| | | | ['timeSum'] | | | | | +| _getWeight(bytes32,uint256) | internal | [] | ['WEEK', 'block.timestamp'] | ['pointsWeight', 'timeWeight'] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)'] | 6 | +| | | | ['changesWeight', 'mapNomineeIds'] | | ['revert NomineeDoesNotExist(bytes32,uint256)'] | | | +| | | | ['mapRemovedNominees', 'pointsWeight'] | | | | | +| | | | ['timeWeight'] | | | | | +| _addNominee(Nominee) | internal | [] | ['WEEK', 'block.timestamp'] | ['mapNomineeIds', 'setNominees'] | ['abi.encode()', 'keccak256(bytes)'] | ['IDispenser(localDispenser).addNominee(nomineeHash)', 'abi.encode(nominee)'] | 4 | +| | | | ['dispenser', 'mapNomineeIds'] | ['timeWeight'] | ['revert NomineeAlreadyExists(bytes32,uint256)', 'revert NomineeRemoved(bytes32,uint256)'] | ['setNominees.push(nominee)'] | | +| | | | ['mapRemovedNominees', 'setNominees'] | | | | | +| addNomineeEVM(address,uint256) | external | [] | ['MAX_EVM_CHAIN_ID'] | [] | ['_addNominee', 'revert Overflow(uint256,uint256)'] | [] | 4 | +| | | | | | ['revert ZeroAddress()', 'revert ZeroValue()'] | | | +| addNomineeNonEVM(bytes32,uint256) | external | [] | ['MAX_EVM_CHAIN_ID'] | [] | ['_addNominee', 'revert Underflow(uint256,uint256)'] | [] | 3 | +| | | | | | ['revert ZeroAddress()'] | | | +| changeOwner(address) | external | [] | ['msg.sender', 'owner'] | ['owner'] | ['revert OwnerOnly(address,address)', 'revert ZeroAddress()'] | [] | 3 | +| changeDispenser(address) | external | [] | ['msg.sender', 'owner'] | ['dispenser'] | ['revert OwnerOnly(address,address)'] | [] | 2 | +| checkpoint() | external | [] | [] | [] | ['_getSum'] | [] | 1 | +| checkpointNominee(bytes32,uint256) | external | [] | [] | [] | ['_getSum', '_getWeight'] | [] | 1 | +| _nomineeRelativeWeight(bytes32,uint256,uint256) | internal | [] | ['WEEK', 'pointsSum'] | [] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)'] | 2 | +| | | | ['pointsWeight'] | | | | | +| nomineeRelativeWeight(bytes32,uint256,uint256) | external | [] | [] | [] | ['_nomineeRelativeWeight'] | [] | 1 | +| nomineeRelativeWeightWrite(bytes32,uint256,uint256) | external | [] | [] | [] | ['_getSum', '_getWeight'] | [] | 1 | +| | | | | | ['_nomineeRelativeWeight'] | | | +| voteForNomineeWeights(bytes32,uint256,uint256) | public | [] | ['MAX_WEIGHT', 'WEEK'] | ['changesSum', 'changesWeight'] | ['_getSum', '_getWeight'] | ['IVEOLAS(ve).getLastUserPoint(msg.sender)', 'IVEOLAS(ve).lockedEnd(msg.sender)'] | 9 | +| | | | ['WEIGHT_VOTE_DELAY', 'block.timestamp'] | ['lastUserVote', 'pointsSum'] | ['_maxAndSub', 'abi.encode()'] | ['abi.encode(Nominee(account,chainId))'] | | +| | | | ['changesSum', 'changesWeight'] | ['pointsWeight', 'voteUserPower'] | ['keccak256(bytes)', 'revert LockExpired(address,uint256,uint256)'] | | | +| | | | ['lastUserVote', 'mapRemovedNominees'] | ['voteUserSlopes'] | ['revert NomineeRemoved(bytes32,uint256)', 'revert Overflow(uint256,uint256)'] | | | +| | | | ['msg.sender', 'pointsSum'] | | ['revert VoteTooOften(address,uint256,uint256)'] | | | +| | | | ['pointsWeight', 've'] | | | | | +| | | | ['voteUserPower', 'voteUserSlopes'] | | | | | +| voteForNomineeWeightsBatch(bytes32[],uint256[],uint256[]) | external | [] | [] | [] | ['revert WrongArrayLength(uint256,uint256)', 'voteForNomineeWeights'] | [] | 3 | +| _maxAndSub(uint256,uint256) | internal | [] | [] | [] | [] | [] | 1 | +| removeNominee(bytes32,uint256) | external | [] | ['WEEK', 'block.timestamp'] | ['mapNomineeIds', 'mapRemovedNominees'] | ['_getSum', '_getWeight'] | ['IDispenser(localDispenser).removeNominee(nomineeHash)', 'abi.encode(nominee)'] | 4 | +| | | | ['dispenser', 'mapNomineeIds'] | ['pointsSum', 'pointsWeight'] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)', 'setNominees.pop()'] | | +| | | | ['msg.sender', 'owner'] | ['setNominees', 'timeSum'] | ['revert NomineeDoesNotExist(bytes32,uint256)', 'revert OwnerOnly(address,address)'] | | | +| | | | ['pointsSum', 'pointsWeight'] | ['timeWeight'] | | | | +| | | | ['setNominees'] | | | | | +| retrieveRemovedNomineeVotingPower(bytes32,uint256) | external | [] | ['block.timestamp', 'changesSum'] | ['changesSum', 'changesWeight'] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)'] | 4 | +| | | | ['changesWeight', 'mapRemovedNominees'] | ['voteUserPower', 'voteUserSlopes'] | ['revert NomineeNotRemoved(bytes32,uint256)', 'revert ZeroValue()'] | | | +| | | | ['msg.sender', 'voteUserPower'] | | | | | +| | | | ['voteUserSlopes'] | | | | | +| getNomineeWeight(bytes32,uint256) | external | [] | ['pointsWeight', 'timeWeight'] | [] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)'] | 1 | +| getWeightsSum() | external | [] | ['pointsSum', 'timeSum'] | [] | [] | [] | 1 | +| getNumNominees() | external | [] | ['setNominees'] | [] | [] | [] | 1 | +| getAllNominees() | external | [] | ['setNominees'] | [] | [] | [] | 1 | +| getNomineeId(bytes32,uint256) | external | [] | ['mapNomineeIds'] | [] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)'] | 1 | +| getNominee(uint256) | external | [] | ['setNominees'] | [] | ['revert Overflow(uint256,uint256)', 'revert ZeroValue()'] | [] | 3 | +| getNominees(uint256,uint256) | external | [] | ['setNominees'] | [] | ['revert Overflow(uint256,uint256)', 'revert ZeroValue()'] | ['new Nominee[](numNominees)'] | 4 | +| getNextAllowedVotingTimes(bytes32[],uint256[],address[]) | external | [] | ['WEIGHT_VOTE_DELAY', 'lastUserVote'] | [] | ['abi.encode()', 'keccak256(bytes)'] | ['abi.encode(nominee)', 'new uint256[](accounts.length)'] | 4 | +| | | | ['mapNomineeIds'] | | ['revert NomineeDoesNotExist(bytes32,uint256)', 'revert WrongArrayLength(uint256,uint256)'] | | | +| slitherConstructorConstantVariables() | internal | [] | [] | ['MAX_EVM_CHAIN_ID', 'MAX_WEIGHT'] | [] | [] | 1 | +| | | | | ['WEEK', 'WEIGHT_VOTE_DELAY'] | | | | ++-----------------------------------------------------------+------------+-----------+------------------------------------------+-----------------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+-----------------------+ + ++-----------+------------+------+-------+----------------+----------------+-----------------------+ +| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | Cyclomatic Complexity | ++-----------+------------+------+-------+----------------+----------------+-----------------------+ ++-----------+------------+------+-------+----------------+----------------+-----------------------+ + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_human-summary.txt b/audits/internal12/analysis/slither_human-summary.txt new file mode 100644 index 0000000..9e4335b --- /dev/null +++ b/audits/internal12/analysis/slither_human-summary.txt @@ -0,0 +1,21 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Compiled with solc +Total number of contracts in source files: 3 +Source lines of code (SLOC) in source files: 408 +Number of assembly lines: 0 +Number of optimization issues: 3 +Number of informational issues: 5 +Number of low issues: 11 +Number of medium issues: 8 +Number of high issues: 6 + ++---------------+-------------+------+------------+--------------+----------+ +| Name | # functions | ERCS | ERC20 info | Complex code | Features | ++---------------+-------------+------+------------+--------------+----------+ +| IDispenser | 2 | | | No | | +| IVEOLAS | 2 | | | No | | +| VoteWeighting | 27 | | | Yes | | ++---------------+-------------+------+------------+--------------+----------+ +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_inheritance-graph.txt b/audits/internal12/analysis/slither_inheritance-graph.txt new file mode 100644 index 0000000..21b4f3d --- /dev/null +++ b/audits/internal12/analysis/slither_inheritance-graph.txt @@ -0,0 +1,5 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers:Inheritance Graph: ./VoteWeighting-flatten.sol.inheritance-graph.dot + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_inheritance.txt b/audits/internal12/analysis/slither_inheritance.txt new file mode 100644 index 0000000..d5bf452 --- /dev/null +++ b/audits/internal12/analysis/slither_inheritance.txt @@ -0,0 +1,21 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers:Inheritance +Child_Contract -> Immediate_Base_Contracts [Not_Immediate_Base_Contracts] ++ IDispenser + ++ IVEOLAS + ++ VoteWeighting + + +Base_Contract -> Immediate_Child_Contracts + [Not_Immediate_Child_Contracts] + ++ IDispenser + ++ IVEOLAS + ++ VoteWeighting + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_modifiers.txt b/audits/internal12/analysis/slither_modifiers.txt new file mode 100644 index 0000000..0d286fc --- /dev/null +++ b/audits/internal12/analysis/slither_modifiers.txt @@ -0,0 +1,52 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Contract IDispenser ++---------------+-----------+ +| Function | Modifiers | ++---------------+-----------+ +| addNominee | [] | +| removeNominee | [] | ++---------------+-----------+ +INFO:Printers: +Contract IVEOLAS ++------------------+-----------+ +| Function | Modifiers | ++------------------+-----------+ +| lockedEnd | [] | +| getLastUserPoint | [] | ++------------------+-----------+ +INFO:Printers: +Contract VoteWeighting ++-------------------------------------+-----------+ +| Function | Modifiers | ++-------------------------------------+-----------+ +| constructor | [] | +| _getSum | [] | +| _getWeight | [] | +| _addNominee | [] | +| addNomineeEVM | [] | +| addNomineeNonEVM | [] | +| changeOwner | [] | +| changeDispenser | [] | +| checkpoint | [] | +| checkpointNominee | [] | +| _nomineeRelativeWeight | [] | +| nomineeRelativeWeight | [] | +| nomineeRelativeWeightWrite | [] | +| voteForNomineeWeights | [] | +| voteForNomineeWeightsBatch | [] | +| _maxAndSub | [] | +| removeNominee | [] | +| retrieveRemovedNomineeVotingPower | [] | +| getNomineeWeight | [] | +| getWeightsSum | [] | +| getNumNominees | [] | +| getAllNominees | [] | +| getNomineeId | [] | +| getNominee | [] | +| getNominees | [] | +| getNextAllowedVotingTimes | [] | +| slitherConstructorConstantVariables | [] | ++-------------------------------------+-----------+ +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_require.txt b/audits/internal12/analysis/slither_require.txt new file mode 100644 index 0000000..d6984c6 --- /dev/null +++ b/audits/internal12/analysis/slither_require.txt @@ -0,0 +1,52 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Contract IDispenser ++---------------+-------------------+ +| Function | require or assert | ++---------------+-------------------+ +| addNominee | | +| removeNominee | | ++---------------+-------------------+ +INFO:Printers: +Contract IVEOLAS ++------------------+-------------------+ +| Function | require or assert | ++------------------+-------------------+ +| lockedEnd | | +| getLastUserPoint | | ++------------------+-------------------+ +INFO:Printers: +Contract VoteWeighting ++-------------------------------------+-------------------+ +| Function | require or assert | ++-------------------------------------+-------------------+ +| constructor | | +| _getSum | | +| _getWeight | | +| _addNominee | | +| addNomineeEVM | | +| addNomineeNonEVM | | +| changeOwner | | +| changeDispenser | | +| checkpoint | | +| checkpointNominee | | +| _nomineeRelativeWeight | | +| nomineeRelativeWeight | | +| nomineeRelativeWeightWrite | | +| voteForNomineeWeights | | +| voteForNomineeWeightsBatch | | +| _maxAndSub | | +| removeNominee | | +| retrieveRemovedNomineeVotingPower | | +| getNomineeWeight | | +| getWeightsSum | | +| getNumNominees | | +| getAllNominees | | +| getNomineeId | | +| getNominee | | +| getNominees | | +| getNextAllowedVotingTimes | | +| slitherConstructorConstantVariables | | ++-------------------------------------+-------------------+ +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_variable-order.txt b/audits/internal12/analysis/slither_variable-order.txt new file mode 100644 index 0000000..8e96d1e --- /dev/null +++ b/audits/internal12/analysis/slither_variable-order.txt @@ -0,0 +1,36 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +IDispenser: ++------+------+------+--------+ +| Name | Type | Slot | Offset | ++------+------+------+--------+ ++------+------+------+--------+ + +IVEOLAS: ++------+------+------+--------+ +| Name | Type | Slot | Offset | ++------+------+------+--------+ ++------+------+------+--------+ + +VoteWeighting: ++----------------------------------+----------------------------------------------------+------+--------+ +| Name | Type | Slot | Offset | ++----------------------------------+----------------------------------------------------+------+--------+ +| VoteWeighting.owner | address | 0 | 0 | +| VoteWeighting.dispenser | address | 1 | 0 | +| VoteWeighting.setNominees | Nominee[] | 2 | 0 | +| VoteWeighting.mapNomineeIds | mapping(bytes32 => uint256) | 3 | 0 | +| VoteWeighting.mapRemovedNominees | mapping(bytes32 => bool) | 4 | 0 | +| VoteWeighting.voteUserSlopes | mapping(address => mapping(bytes32 => VotedSlope)) | 5 | 0 | +| VoteWeighting.voteUserPower | mapping(address => uint256) | 6 | 0 | +| VoteWeighting.lastUserVote | mapping(address => mapping(bytes32 => uint256)) | 7 | 0 | +| VoteWeighting.pointsWeight | mapping(bytes32 => mapping(uint256 => Point)) | 8 | 0 | +| VoteWeighting.changesWeight | mapping(bytes32 => mapping(uint256 => uint256)) | 9 | 0 | +| VoteWeighting.timeWeight | mapping(bytes32 => uint256) | 10 | 0 | +| VoteWeighting.pointsSum | mapping(uint256 => Point) | 11 | 0 | +| VoteWeighting.changesSum | mapping(uint256 => uint256) | 12 | 0 | +| VoteWeighting.timeSum | uint256 | 13 | 0 | ++----------------------------------+----------------------------------------------------+------+--------+ + +INFO:Slither:. analyzed (3 contracts) diff --git a/audits/internal12/analysis/slither_vars-and-auth.txt b/audits/internal12/analysis/slither_vars-and-auth.txt new file mode 100644 index 0000000..476954a --- /dev/null +++ b/audits/internal12/analysis/slither_vars-and-auth.txt @@ -0,0 +1,53 @@ +'solc --version' running +'solc ./VoteWeighting-flatten.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes --allow-paths .,/home/andrey/valory/autonolas-governance/audits/internal12/analysis/contracts' running +INFO:Printers: +Contract IDispenser ++---------------+-------------------------+--------------------------+ +| Function | State variables written | Conditions on msg.sender | ++---------------+-------------------------+--------------------------+ +| addNominee | [] | [] | +| removeNominee | [] | [] | ++---------------+-------------------------+--------------------------+ + +Contract IVEOLAS ++------------------+-------------------------+--------------------------+ +| Function | State variables written | Conditions on msg.sender | ++------------------+-------------------------+--------------------------+ +| lockedEnd | [] | [] | +| getLastUserPoint | [] | [] | ++------------------+-------------------------+--------------------------+ + +Contract VoteWeighting ++-------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+--------------------------+ +| Function | State variables written | Conditions on msg.sender | ++-------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+--------------------------+ +| constructor | ['owner', 'setNominees', 'timeSum', 've'] | [] | +| _getSum | ['pointsSum', 'timeSum'] | [] | +| _getWeight | ['pointsWeight', 'timeWeight'] | [] | +| _addNominee | ['mapNomineeIds', 'setNominees', 'timeWeight'] | [] | +| addNomineeEVM | ['mapNomineeIds', 'setNominees', 'timeWeight'] | [] | +| addNomineeNonEVM | ['mapNomineeIds', 'setNominees', 'timeWeight'] | [] | +| changeOwner | ['owner'] | ['msg.sender != owner'] | +| changeDispenser | ['dispenser'] | ['msg.sender != owner'] | +| checkpoint | ['pointsSum', 'timeSum'] | [] | +| checkpointNominee | ['pointsSum', 'pointsWeight', 'timeSum', 'timeWeight'] | [] | +| _nomineeRelativeWeight | [] | [] | +| nomineeRelativeWeight | [] | [] | +| nomineeRelativeWeightWrite | ['pointsSum', 'pointsWeight', 'timeSum', 'timeWeight'] | [] | +| voteForNomineeWeights | ['changesSum', 'changesWeight', 'lastUserVote', 'pointsSum', 'pointsWeight', 'timeSum', 'timeWeight', 'voteUserPower', 'voteUserSlopes'] | [] | +| voteForNomineeWeightsBatch | ['changesSum', 'changesWeight', 'lastUserVote', 'pointsSum', 'pointsWeight', 'timeSum', 'timeWeight', 'voteUserPower', 'voteUserSlopes'] | [] | +| _maxAndSub | [] | [] | +| removeNominee | ['mapNomineeIds', 'mapRemovedNominees', 'pointsSum', 'pointsWeight', 'setNominees', 'timeSum', 'timeWeight'] | ['msg.sender != owner'] | +| retrieveRemovedNomineeVotingPower | ['changesSum', 'changesWeight', 'voteUserPower', 'voteUserSlopes'] | [] | +| getNomineeWeight | [] | [] | +| getWeightsSum | [] | [] | +| getNumNominees | [] | [] | +| getAllNominees | [] | [] | +| getNomineeId | [] | [] | +| getNominee | [] | [] | +| getNominees | [] | [] | +| getNextAllowedVotingTimes | [] | [] | +| slitherConstructorConstantVariables | ['MAX_EVM_CHAIN_ID', 'MAX_WEIGHT', 'WEEK', 'WEIGHT_VOTE_DELAY'] | [] | ++-------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------+--------------------------+ + +INFO:Slither:. analyzed (3 contracts) diff --git a/contracts/test/EchidnaVoteWeightingAssert.sol b/contracts/test/EchidnaVoteWeightingAssert.sol index 8550487..748c3eb 100644 --- a/contracts/test/EchidnaVoteWeightingAssert.sol +++ b/contracts/test/EchidnaVoteWeightingAssert.sol @@ -10,7 +10,6 @@ contract EchidnaVoteWeightingAssert { OLAS olas; veOLAS ve; VoteWeightingFuzzing vw; - uint160 constant FAKE_OLAS = 7; uint256 constant oneOLASBalance = 1 ether; uint256 constant fourYear = 4 * 365 * 86400; @@ -34,7 +33,7 @@ contract EchidnaVoteWeightingAssert { } // voteForNomineeWeights_assert(0xdeadbeef,1,0,4495678220902361,1124857) - function voteForNomineeWeights_assert(address nominee, uint32 chainId, uint16 weight, uint256 amount, uint32 unlockTime) external { + function voteForNomineeWeights_assert(address account, uint32 chainId, uint16 weight, uint256 amount, uint32 unlockTime) external { require(block.timestamp > 0); require(block.timestamp > ts); require(unlockTime < fourYear); @@ -52,7 +51,8 @@ contract EchidnaVoteWeightingAssert { (uint128 lockedAmount,) = ve.mapLockedBalances(address(this)); assert(lockedAmount > 0); } - vw.addNominee(nominee, chainId); + vw.addNomineeEVM(account, chainId); + bytes32 nominee = bytes32(uint256(uint160(account))); uint256 id = vw.getNomineeId(nominee, chainId); uint256 num = vw.getNumNominees(); assert(id > 0); diff --git a/contracts/test/VoteWeightingFuzzing.sol b/contracts/test/VoteWeightingFuzzing.sol index 5ea83cb..e675023 100644 --- a/contracts/test/VoteWeightingFuzzing.sol +++ b/contracts/test/VoteWeightingFuzzing.sol @@ -1,8 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import "../interfaces/IErrors.sol"; +// Dispenser interface +interface IDispenser { + /// @dev Records nominee addition in dispenser. + /// @param nomineeHash Nominee hash. + function addNominee(bytes32 nomineeHash) external; + + /// @dev Records nominee removal. + /// @param nomineeHash Nominee hash. + function removeNominee(bytes32 nomineeHash) external; +} +// veOLAS interface interface IVEOLAS { // Structure for voting escrow points // The struct size is two storage slots of 2 * uint256 (128 + 128 + 64 + 64 + 128) @@ -31,25 +41,93 @@ interface IVEOLAS { function getLastUserPoint(address account) external view returns (PointVoting memory pv); } -error NomineeDoesNotExist(address nominee, uint256 chainId); -error NomineeAlreadyExists(address nominee, uint256 chainId); +/// @dev Only `owner` has a privilege, but the `sender` was provided. +/// @param sender Sender address. +/// @param owner Required sender address as an owner. +error OwnerOnly(address sender, address owner); + +/// @dev Provided zero address. +error ZeroAddressVW(); + +/// @dev Zero value when it has to be different from zero. +error ZeroValue(); + +/// @dev Wrong length of two arrays. +/// @param numValues1 Number of values in a first array. +/// @param numValues2 Number of values in a second array. +error WrongArrayLength(uint256 numValues1, uint256 numValues2); + +/// @dev Value overflow. +/// @param provided Overflow value. +/// @param max Maximum possible value. +error Overflow(uint256 provided, uint256 max); + +/// @dev Underflow value. +/// @param provided Provided value. +/// @param expected Minimum expected value. +error Underflow(uint256 provided, uint256 expected); + +/// @dev Nominee does not exist. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeDoesNotExist(bytes32 account, uint256 chainId); + +/// @dev Nominee already exists. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeAlreadyExists(bytes32 account, uint256 chainId); + +/// @dev Value lock is expired. +/// @param account Address that is checked for the locked value. +/// @param deadline The lock expiration deadline. +/// @param curTime Current timestamp. +error LockExpired(address account, uint256 deadline, uint256 curTime); + +/// @dev The vote has been performed already. +/// @param voter Voter address. +/// @param curTime Current time. +/// @param nextAllowedVotingTime Next allowed voting time. error VoteTooOften(address voter, uint256 curTime, uint256 nextAllowedVotingTime); +/// @dev Nominee is not in the removed nominee map. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeNotRemoved(bytes32 account, uint256 chainId); + +/// @dev Nominee is in the removed nominee map. +/// @param account Nominee account address. +/// @param chainId Nominee chain Id. +error NomineeRemoved(bytes32 account, uint256 chainId); + +// Point struct struct Point { uint256 bias; uint256 slope; } +// Voted slope struct struct VotedSlope { uint256 slope; uint256 power; uint256 end; } -contract VoteWeightingFuzzing is IErrors { - event NewNomineeWeight(address indexed nominee, uint256 chainId, uint256 weight, uint256 totalWeight); - event VoteForNominee(address indexed user, address indexed nominee, uint256 chainId, uint256 weight); - event NewNominee(address nominee, uint256 chainId); +// Nominee struct +struct Nominee { + bytes32 account; + uint256 chainId; +} + + +/// @title VoteWeighting - Smart contract for Vote Weighting with specific nominees composed of address and chain Id +/// @author Aleksandr Kuperman - +/// @author Andrey Lebedev - +/// @author Mariapia Moscatiello - +contract VoteWeightingFuzzing { + event OwnerUpdated(address indexed owner); + event VoteForNominee(address indexed user, bytes32 indexed nominee, uint256 chainId, uint256 weight); + event AddNominee(bytes32 indexed account, uint256 chainId, uint256 id); + event RemoveNominee(bytes32 indexed account, uint256 chainId, uint256 newSum); // 7 * 86400 seconds - all future times are rounded by week uint256 public constant WEEK = 604_800; @@ -58,24 +136,27 @@ contract VoteWeightingFuzzing is IErrors { // Max weight amount uint256 public constant MAX_WEIGHT = 10_000; // Maximum chain Id as per EVM specs - uint256 public constant MAX_CHAIN_ID = type(uint64).max / 2 - 36; + uint256 public constant MAX_EVM_CHAIN_ID = type(uint64).max / 2 - 36; // veOLAS contract address address public immutable ve; - - bool public callVoteForNomineeWeights = false; - - // TODO: Convert both to cyclic map? - // Set of (chainId | nominee) - uint256[] public setNominees; - // Mapping of (chainId | nominee) => nominee Id - mapping(uint256 => uint256) public mapNomineeIds; - - // user -> (chainId | nominee) -> VotedSlope - mapping(address => mapping(uint256 => VotedSlope)) public voteUserSlopes; + // Contract owner address + address public owner; + // Dispenser contract + address public dispenser; + + // Set of Nominee structs + Nominee[] public setNominees; + // Mapping of hash(Nominee struct) => nominee Id + mapping(bytes32 => uint256) public mapNomineeIds; + // Mapping of hash(Nominee struct) => previously removed nominee flag + mapping(bytes32 => bool) public mapRemovedNominees; + + // user -> hash(Nominee struct) -> VotedSlope + mapping(address => mapping(bytes32 => VotedSlope)) public voteUserSlopes; // Total vote power used by user mapping(address => uint256) public voteUserPower; - // Last user vote's timestamp for each (chainId | nominee) - mapping(address => mapping(uint256 => uint256)) public lastUserVote; + // Last user vote's timestamp for each hash(Nominee struct) + mapping(address => mapping(bytes32 => uint256)) public lastUserVote; // Past and scheduled points for nominee weight, sum of weights per type, total weight // Point is for bias+slope @@ -83,12 +164,12 @@ contract VoteWeightingFuzzing is IErrors { // time_* are for the last change timestamp // timestamps are rounded to whole weeks - // (chainId | nominee) -> time -> Point - mapping(uint256 => mapping(uint256 => Point)) public pointsWeight; - // (chainId | nominee) -> time -> slope - mapping(uint256 => mapping(uint256 => uint256)) public changesWeight; - // (chainId | nominee) -> last scheduled time (next week) - mapping(uint256 => uint256) public timeWeight; + // hash(Nominee struct) -> time -> Point + mapping(bytes32 => mapping(uint256 => Point)) public pointsWeight; + // hash(Nominee struct) -> time -> slope + mapping(bytes32 => mapping(uint256 => uint256)) public changesWeight; + // hash(Nominee struct) -> last scheduled time (next week) + mapping(bytes32 => uint256) public timeWeight; // time -> Point mapping(uint256 => Point) public pointsSum; @@ -96,19 +177,22 @@ contract VoteWeightingFuzzing is IErrors { mapping(uint256 => uint256) public changesSum; // last scheduled time (next week) uint256 public timeSum; + // for fuzzing + bool public callVoteForNomineeWeights = false; /// @dev Contract constructor. - /// @param _ve `VotingEscrow` contract address. + /// @param _ve Voting Escrow contract address. constructor(address _ve) { // Check for the zero address if (_ve == address(0)) { - revert ZeroAddress(); + revert ZeroAddressVW(); } // Set initial parameters + owner = msg.sender; ve = _ve; timeSum = block.timestamp / WEEK * WEEK; - setNominees.push(0); + setNominees.push(Nominee(bytes32(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. @@ -141,25 +225,22 @@ contract VoteWeightingFuzzing is IErrors { } /// @dev Fill historic nominee weights week-over-week for missed checkins and return the total for the future week. - /// @param nominee Address of the nominee. - /// @param chainId Chain Id. + /// @param account Nominee account address in bytes32 form. + /// @param chainId Nominee chain Id. /// @return Nominee weight. - function _getWeight(address nominee, uint256 chainId) internal returns (uint256) { - // Push a pair of key defining variables into one key - // Nominee address and chain Id - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; - - // Check that the nominee exists - if (mapNomineeIds[nomineeChainId] == 0) { - revert NomineeDoesNotExist(nominee, chainId); + function _getWeight(bytes32 account, uint256 chainId) internal returns (uint256) { + // Construct the nominee struct + Nominee memory nominee = Nominee(account, chainId); + + // Check that the nominee exists or has been removed + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + if (!mapRemovedNominees[nomineeHash] && mapNomineeIds[nomineeHash] == 0) { + revert NomineeDoesNotExist(account, chainId); } // t is always > 0 as it is set during the addNominee() call - uint256 t = timeWeight[nomineeChainId]; - Point memory pt = pointsWeight[nomineeChainId][t]; + uint256 t = timeWeight[nomineeHash]; + Point memory pt = pointsWeight[nomineeHash][t]; for (uint256 i = 0; i < 500; i++) { if (t > block.timestamp) { break; @@ -168,56 +249,124 @@ contract VoteWeightingFuzzing is IErrors { uint256 dBias = pt.slope * WEEK; if (pt.bias > dBias) { pt.bias -= dBias; - uint256 dSlope = changesWeight[nomineeChainId][t]; + uint256 dSlope = changesWeight[nomineeHash][t]; pt.slope -= dSlope; } else { pt.bias = 0; pt.slope = 0; } - pointsWeight[nomineeChainId][t] = pt; + pointsWeight[nomineeHash][t] = pt; if (t > block.timestamp) { - timeWeight[nomineeChainId] = t; + timeWeight[nomineeHash] = t; } } return pt.bias; } /// @dev Add nominee address along with the chain Id. - /// @param nominee Address of the nominee. + /// @param nominee Nominee account address and chainId. + function _addNominee(Nominee memory nominee) internal { + // Check for the nominee existence + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + if (mapNomineeIds[nomineeHash] > 0) { + revert NomineeAlreadyExists(nominee.account, nominee.chainId); + } + + // Check for the previously removed nominee + if (mapRemovedNominees[nomineeHash]) { + revert NomineeRemoved(nominee.account, nominee.chainId); + } + + uint256 id = setNominees.length; + mapNomineeIds[nomineeHash] = id; + // Push the nominee into the list + setNominees.push(nominee); + + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + timeWeight[nomineeHash] = nextTime; + + // Enable nominee in dispenser, if applicable + address localDispenser = dispenser; + if (localDispenser != address(0)) { + IDispenser(localDispenser).addNominee(nomineeHash); + } + + emit AddNominee(nominee.account, nominee.chainId, id); + } + + /// @dev Add EVM nominee address along with the chain Id. + /// @param account Address of the nominee. /// @param chainId Chain Id. - function addNominee(address nominee, uint256 chainId) external { + function addNomineeEVM(address account, uint256 chainId) external { // Check for the zero address - if (nominee == address(0)) { - revert ZeroAddress(); + if (account == address(0)) { + revert ZeroAddressVW(); } - // Check for the chain Id + // Check for zero chain Id if (chainId == 0) { revert ZeroValue(); } - else if (chainId > MAX_CHAIN_ID) { - revert Overflow(chainId, MAX_CHAIN_ID); + + // Check for the chain Id overflow + if (chainId > MAX_EVM_CHAIN_ID) { + revert Overflow(chainId, MAX_EVM_CHAIN_ID); } - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; + Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); - // Check for the nominee existence - if (mapNomineeIds[nomineeChainId] > 0) { - revert NomineeAlreadyExists(nominee, chainId); + // Record nominee instance + _addNominee(nominee); + } + + /// @dev Add Non-EVM nominee address along with the chain Id. + /// @param account Address of the nominee in byte32 standard. + /// @param chainId Chain Id. + function addNomineeNonEVM(bytes32 account, uint256 chainId) external { + // Check for the zero address + if (account == bytes32(0)) { + revert ZeroAddressVW(); } - mapNomineeIds[nomineeChainId] = setNominees.length; - // Push the nominee into the list - setNominees.push(nomineeChainId); - uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; - timeWeight[nomineeChainId] = nextTime; + // Check for the chain Id underflow + if (MAX_EVM_CHAIN_ID >= chainId) { + revert Underflow(chainId, MAX_EVM_CHAIN_ID + 1); + } + + Nominee memory nominee = Nominee(account, chainId); - emit NewNominee(nominee, chainId); + // Record nominee instance + _addNominee(nominee); + } + + /// @dev Changes the owner address. + /// @param newOwner Address of a new owner. + function changeOwner(address newOwner) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + // Check for the zero address + if (newOwner == address(0)) { + revert ZeroAddressVW(); + } + + owner = newOwner; + emit OwnerUpdated(newOwner); + } + + /// @dev Changes the dispenser contract address. + /// @notice Dispenser can a zero address if the contract needs to serve a general purpose. + /// @param newDispenser New dispenser contract address. + function changeDispenser(address newDispenser) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + dispenser = newDispenser; } /// @dev Checkpoint to fill data common for all nominees. @@ -226,36 +375,33 @@ contract VoteWeightingFuzzing is IErrors { } /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees. - /// @param nominee Address of the nominee. + /// @param account Address of the nominee. /// @param chainId Chain Id. - function checkpointNominee(address nominee, uint256 chainId) external { - _getWeight(nominee, chainId); + function checkpointNominee(bytes32 account, uint256 chainId) external { + _getWeight(account, chainId); _getSum(); } /// @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. - /// @param nominee Address of the nominee. + /// @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. /// @return weight Value of relative weight normalized to 1e18. /// @return totalSum Sum of nominee weights. function _nomineeRelativeWeight( - address nominee, + bytes32 account, uint256 chainId, uint256 time ) internal view returns (uint256 weight, uint256 totalSum) { uint256 t = time / WEEK * WEEK; totalSum = pointsSum[t].bias; - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); if (totalSum > 0) { - uint256 nomineeWeight = pointsWeight[nomineeChainId][t].bias; + uint256 nomineeWeight = pointsWeight[nomineeHash][t].bias; weight = 1e18 * nomineeWeight / totalSum; } } @@ -263,48 +409,49 @@ contract VoteWeightingFuzzing is IErrors { /// @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. - /// @param nominee Address of the nominee. + /// @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. /// @return weight Value of relative weight normalized to 1e18. /// @return totalSum Sum of nominee weights. function nomineeRelativeWeight( - address nominee, + bytes32 account, uint256 chainId, uint256 time ) external view returns (uint256 weight, uint256 totalSum) { - (weight, totalSum) = _nomineeRelativeWeight(nominee, chainId, time); + (weight, 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. - /// @param nominee Address of the nominee. + /// @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. /// @return weight Value of relative weight normalized to 1e18. /// @return totalSum Sum of nominee weights. function nomineeRelativeWeightWrite( - address nominee, + bytes32 account, uint256 chainId, uint256 time ) external returns (uint256 weight, uint256 totalSum) { - _getWeight(nominee, chainId); + _getWeight(account, chainId); _getSum(); - (weight, totalSum) = _nomineeRelativeWeight(nominee, chainId, time); + (weight, totalSum) = _nomineeRelativeWeight(account, chainId, time); } /// @dev Allocate voting power for changing pool weights. - /// @param nominee Address of the nominee the `msg.sender` votes for. + /// @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. - function voteForNomineeWeights(address nominee, uint256 chainId, uint256 weight) public { + function voteForNomineeWeights(bytes32 account, uint256 chainId, uint256 weight) public { + // Get the nominee hash + bytes32 nomineeHash = keccak256(abi.encode(Nominee(account, chainId))); - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; + // Check for the previously removed nominee + if (mapRemovedNominees[nomineeHash]) { + revert NomineeRemoved(account, chainId); + } uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); @@ -321,13 +468,13 @@ contract VoteWeightingFuzzing is IErrors { } // Check for the last voting time - uint256 nextAllowedVotingTime = lastUserVote[msg.sender][nomineeChainId] + WEIGHT_VOTE_DELAY; + uint256 nextAllowedVotingTime = lastUserVote[msg.sender][nomineeHash] + WEIGHT_VOTE_DELAY; if (nextAllowedVotingTime > block.timestamp) { revert VoteTooOften(msg.sender, block.timestamp, nextAllowedVotingTime); } // Prepare old and new slopes and biases - VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeChainId]; + VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; uint256 oldBias; if (oldSlope.end > nextTime) { oldBias = oldSlope.slope * (oldSlope.end - nextTime); @@ -351,51 +498,49 @@ contract VoteWeightingFuzzing is IErrors { // Remove old and schedule new slope changes // Remove slope changes for old slopes // Schedule recording of initial slope for nextTime - pointsWeight[nomineeChainId][nextTime].bias = _maxAndSub(_getWeight(nominee, chainId) + newBias, oldBias); - + pointsWeight[nomineeHash][nextTime].bias = _maxAndSub(_getWeight(account, chainId) + newBias, oldBias); pointsSum[nextTime].bias = _maxAndSub(_getSum() + newBias, oldBias); if (oldSlope.end > nextTime) { - pointsWeight[nomineeChainId][nextTime].slope = _maxAndSub(pointsWeight[nomineeChainId][nextTime].slope + newSlope.slope, oldSlope.slope); + pointsWeight[nomineeHash][nextTime].slope = + _maxAndSub(pointsWeight[nomineeHash][nextTime].slope + newSlope.slope, oldSlope.slope); pointsSum[nextTime].slope = _maxAndSub(pointsSum[nextTime].slope + newSlope.slope, oldSlope.slope); } else { - pointsWeight[nomineeChainId][nextTime].slope += newSlope.slope; + pointsWeight[nomineeHash][nextTime].slope += newSlope.slope; pointsSum[nextTime].slope += newSlope.slope; } if (oldSlope.end > block.timestamp) { // Cancel old slope changes if they still didn't happen - changesWeight[nomineeChainId][oldSlope.end] -= oldSlope.slope; + changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; changesSum[oldSlope.end] -= oldSlope.slope; } // Add slope changes for new slopes - changesWeight[nomineeChainId][newSlope.end] += newSlope.slope; + changesWeight[nomineeHash][newSlope.end] += newSlope.slope; changesSum[newSlope.end] += newSlope.slope; - voteUserSlopes[msg.sender][nomineeChainId] = newSlope; + voteUserSlopes[msg.sender][nomineeHash] = newSlope; // Record last action time - lastUserVote[msg.sender][nomineeChainId] = block.timestamp; - assert(lastUserVote[msg.sender][nomineeChainId] > 0); - - callVoteForNomineeWeights = true; - emit VoteForNominee(msg.sender, nominee, chainId, weight); + lastUserVote[msg.sender][nomineeHash] = block.timestamp; + + emit VoteForNominee(msg.sender, account, chainId, weight); } /// @dev Allocate voting power for changing pool weights in batch. - /// @param nominees Set of nominees the `msg.sender` votes for. + /// @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. function voteForNomineeWeightsBatch( - address[] memory nominees, + bytes32[] memory accounts, uint256[] memory chainIds, uint256[] memory weights ) external { - if (nominees.length != chainIds.length || nominees.length != weights.length) { - revert WrongArrayLength(nominees.length, weights.length); + if (accounts.length != chainIds.length || accounts.length != weights.length) { + revert WrongArrayLength(accounts.length, weights.length); } // Traverse all accounts and weights - for (uint256 i = 0; i < nominees.length; ++i) { - voteForNomineeWeights(nominees[i], chainIds[i], weights[i]); + for (uint256 i = 0; i < accounts.length; ++i) { + voteForNomineeWeights(accounts[i], chainIds[i], weights[i]); } } @@ -403,18 +548,101 @@ contract VoteWeightingFuzzing is IErrors { return a > b ? a - b : 0; } + /// @dev Removes nominee from the contract and zeros its weight. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + function removeNominee(bytes32 account, uint256 chainId) external { + // Check for the contract ownership + if (msg.sender != owner) { + revert OwnerOnly(owner, msg.sender); + } + + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Get the nominee id in the nominee set + uint256 id = mapNomineeIds[nomineeHash]; + if (id == 0) { + revert NomineeDoesNotExist(account, chainId); + } + + // Set nominee weight to zero + uint256 oldWeight = _getWeight(account, chainId); + uint256 oldSum = _getSum(); + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + pointsWeight[nomineeHash][nextTime].bias = 0; + timeWeight[nomineeHash] = nextTime; + + // Account for the the sum weight change + uint256 newSum = oldSum - oldWeight; + pointsSum[nextTime].bias = newSum; + timeSum = nextTime; + + // Add to the removed nominee map + mapRemovedNominees[nomineeHash] = true; + + // Remove nominee in dispenser, if applicable + address localDispenser = dispenser; + if (localDispenser != address(0)) { + IDispenser(localDispenser).removeNominee(nomineeHash); + } + + // Remove nominee from the map + mapNomineeIds[nomineeHash] = 0; + // Shuffle the current last nominee id in the set to be placed to the removed one + nominee = setNominees[setNominees.length - 1]; + nomineeHash = keccak256(abi.encode(nominee)); + mapNomineeIds[nomineeHash] = id; + setNominees[id] = nominee; + // Pop the last element from the set + setNominees.pop(); + + emit RemoveNominee(account, chainId, newSum); + } + + /// @dev Retrieves user voting power from a removed nominee. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + function retrieveRemovedNomineeVotingPower(bytes32 account, uint256 chainId) external { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Check that the nominee is removed + if (!mapRemovedNominees[nomineeHash]) { + revert NomineeNotRemoved(account, chainId); + } + + // Get the user old slope + VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash]; + if (oldSlope.power == 0) { + revert ZeroValue(); + } + + // Cancel old slope changes if they still didn't happen + if (oldSlope.end > block.timestamp) { + changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope; + changesSum[oldSlope.end] -= oldSlope.slope; + } + + // Update the voting power + uint256 powerUsed = voteUserPower[msg.sender]; + powerUsed = powerUsed - oldSlope.power; + voteUserPower[msg.sender] = powerUsed; + delete voteUserSlopes[msg.sender][nomineeHash]; + } + /// @dev Get current nominee weight. - /// @param nominee Address of the nominee. + /// @param account Address of the nominee in bytes32 form. /// @param chainId Chain Id. /// @return Nominee weight. - function getNomineeWeight(address nominee, uint256 chainId) external view returns (uint256) { - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; - - return pointsWeight[nomineeChainId][timeWeight[nomineeChainId]].bias; + function getNomineeWeight(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)); + + return pointsWeight[nomineeHash][timeWeight[nomineeHash]].bias; } /// @dev Get sum of nominee weights. @@ -423,33 +651,37 @@ contract VoteWeightingFuzzing is IErrors { return pointsSum[timeSum].bias; } - /// @dev Get the number of nominees. + /// @dev Get the total number of nominees. /// @notice The zero-th default nominee Id with id == 0 does not count. /// @return Total number of nominees. function getNumNominees() external view returns (uint256) { return setNominees.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; + } + /// @dev Gets the nominee Id in the global nominees set. - /// @param nominee Nominee address. + /// @param account Nominee address in bytes32 form. /// @param chainId Chain Id. - /// @return id Nominee Id in the global set of (nominee | chainId) values. - function getNomineeId(address nominee, uint256 chainId) external view returns (uint256 id) { - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; - - id = mapNomineeIds[nomineeChainId]; + /// @return id Nominee Id in the global set of Nominee struct values. + function getNomineeId(bytes32 account, uint256 chainId) external view returns (uint256 id) { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + id = 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 | chainId) values. - /// @return nominee Nominee address. - /// @return chainId Chain Id. - function getNominee(uint256 id) external view returns (address nominee, uint256 chainId) { + /// @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) { // Get the total number of nominees in the contract uint256 totalNumNominees = setNominees.length - 1; // Check for the zero id or the overflow @@ -458,24 +690,19 @@ contract VoteWeightingFuzzing is IErrors { } else if (id > totalNumNominees) { revert Overflow(id, totalNumNominees); } - - uint256 nomineeChainId = setNominees[id]; - // Extract the nominee address - nominee = address(uint160(uint256(nomineeChainId))); - // Extract chain Id - chainId = nomineeChainId >> 160; + + nominee = setNominees[id]; } /// @dev Get the set of nominee addresses and corresponding chain Ids. /// @notice The zero-th default nominee Id with id == 0 does not count. - /// @param startId Start Id of the nominee in the global set of (nominee | chainId) values. + /// @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 addresses. - /// @return chainIds Set of corresponding chain Ids. + /// @return nominees Set of nominee accounts in bytes32 form and chain Ids. function getNominees( uint256 startId, uint256 numNominees - ) external view returns (address[] memory nominees, uint256[] memory chainIds) + ) external view returns (Nominee[] memory nominees) { // Check for the zero id or the overflow if (startId == 0 || numNominees == 0) { @@ -492,18 +719,48 @@ contract VoteWeightingFuzzing is IErrors { revert Overflow(endId, totalNumNominees); } - // Allocate - nominees = new address[](numNominees); - chainIds = new uint256[](numNominees); + // Allocate the nominee array + nominees = new Nominee[](numNominees); // Traverse selected nominees for (uint256 i = 0; i < numNominees; ++i) { uint256 id = i + startId; - uint256 nomineeChainId = setNominees[id]; - // Extract the nominee address - nominees[i] = address(uint160(uint256(nomineeChainId))); - // Extract chain Id - chainIds[i] = nomineeChainId >> 160; + // Get the nominee struct + nominees[i] = setNominees[id]; + } + } + + /// @dev Gets next allowed voting time for selected nominees and voters. + /// @notice The function does not check for repeated nominees and voters. + /// @param accounts Set of nominee account addresses. + /// @param chainIds Corresponding set of chain Ids. + /// @param voters Corresponding set of voters for specified nominees. + function getNextAllowedVotingTimes( + bytes32[] memory accounts, + uint256[] memory chainIds, + address[] memory voters + ) external view returns (uint256[] memory nextAllowedVotingTimes) { + // Check array lengths + if (accounts.length != chainIds.length || accounts.length != voters.length) { + revert WrongArrayLength(accounts.length, chainIds.length); + } + + // Allocate the times array + nextAllowedVotingTimes = new uint256[](accounts.length); + + // Traverse nominees and get next available voting times + for (uint256 i = 0; i < accounts.length; ++i) { + // Get the nominee struct and hash + Nominee memory nominee = Nominee(accounts[i], chainIds[i]); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + // Check for nominee existence + if (mapNomineeIds[nomineeHash] == 0) { + revert NomineeDoesNotExist(accounts[i], chainIds[i]); + } + + // Calculate next allowed voting times + nextAllowedVotingTimes[i] = lastUserVote[voters[i]][nomineeHash] + WEIGHT_VOTE_DELAY; } } @@ -511,13 +768,13 @@ contract VoteWeightingFuzzing is IErrors { function setCallVoteForNomineeWeights(bool flag) external { callVoteForNomineeWeights = flag; } + /// @dev For fuzzing only - function getlastUserVote(address nominee, uint256 chainId) external view returns (uint256) { - // Push a pair of key defining variables into one key - // nominee occupies first 160 bits - uint256 nomineeChainId = uint256(uint160(nominee)); - // chain Id occupies no more than next 64 bits - nomineeChainId |= chainId << 160; - return lastUserVote[msg.sender][nomineeChainId]; + function getlastUserVote(bytes32 _nominee, uint256 chainId) external view returns (uint256) { + // account occupies first 160 bits + Nominee memory nominee = Nominee(_nominee, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + return lastUserVote[msg.sender][nomineeHash]; } } \ No newline at end of file