From 50df02665dbc7677cc30c1f39dace58e9382c56d Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 26 Apr 2024 10:29:13 +0100 Subject: [PATCH 1/3] refactor: accounting for non-evm chains for vote weighting --- contracts/VoteWeighting.sol | 305 +++++++++++++++++++----------------- hardhat.config.js | 16 +- package.json | 2 +- yarn.lock | 114 ++++++-------- 4 files changed, 229 insertions(+), 208 deletions(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index a89f716..08099dd 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -31,8 +31,25 @@ 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 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 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); struct Point { @@ -46,10 +63,15 @@ struct VotedSlope { uint256 end; } +struct Nominee { + bytes32 account; + uint256 chainId; +} + contract VoteWeighting 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); + event NewNomineeWeight(bytes32 indexed nominee, uint256 chainId, uint256 weight, uint256 totalWeight); + event VoteForNominee(address indexed user, bytes32 indexed nominee, uint256 chainId, uint256 weight); + event NewNominee(bytes32 account, uint256 chainId); // 7 * 86400 seconds - all future times are rounded by week uint256 public constant WEEK = 604_800; @@ -58,22 +80,21 @@ contract VoteWeighting 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; - // TODO: Convert both to cyclic map? - // Set of (chainId | nominee) - uint256[] public setNominees; - // Mapping of (chainId | nominee) => nominee Id - mapping(uint256 => uint256) public mapNomineeIds; + // Set of Nominee structs + Nominee[] public setNominees; + // Mapping of hash(Nominee struct) => nominee Id + mapping(bytes32 => uint256) public mapNomineeIds; - // user -> (chainId | nominee) -> VotedSlope - mapping(address => mapping(uint256 => VotedSlope)) public voteUserSlopes; + // 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 @@ -81,12 +102,12 @@ contract VoteWeighting 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; @@ -106,7 +127,7 @@ contract VoteWeighting is IErrors { // Set initial parameters 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. @@ -139,25 +160,22 @@ contract VoteWeighting 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; + function _getWeight(bytes32 account, uint256 chainId) internal returns (uint256) { + // Construct the nominee struct + Nominee memory nominee = Nominee(account, chainId); // Check that the nominee exists - if (mapNomineeIds[nomineeChainId] == 0) { - revert NomineeDoesNotExist(nominee, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + if (mapNomineeIds[nomineeHash] == 0) { + revert NomineeDoesNotExist(nominee.account, nominee.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; @@ -166,56 +184,82 @@ contract VoteWeighting 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); + } + mapNomineeIds[nomineeHash] = setNominees.length; + // Push the nominee into the list + setNominees.push(nominee); + + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; + timeWeight[nomineeHash] = nextTime; + + emit NewNominee(nominee.account, nominee.chainId); + } + + /// @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 addNominee(address account, uint256 chainId) external { // Check for the zero address - if (nominee == address(0)) { + if (account == address(0)) { revert ZeroAddress(); } - // 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 ZeroAddress(); } - 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 Checkpoint to fill data common for all nominees. @@ -224,36 +268,33 @@ contract VoteWeighting 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; } } @@ -261,47 +302,44 @@ contract VoteWeighting 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 { - // 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; + function voteForNomineeWeights(bytes32 account, uint256 chainId, uint256 weight) public { + // Get the nominee hash + bytes32 nomineeHash = keccak256(abi.encode(Nominee(account, chainId))); uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); @@ -318,13 +356,13 @@ contract VoteWeighting 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); @@ -348,48 +386,49 @@ contract VoteWeighting 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; + lastUserVote[msg.sender][nomineeHash] = block.timestamp; - emit VoteForNominee(msg.sender, nominee, chainId, weight); + 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]); } } @@ -398,17 +437,15 @@ contract VoteWeighting is IErrors { } /// @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. @@ -417,7 +454,7 @@ contract VoteWeighting 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) { @@ -425,25 +462,22 @@ contract VoteWeighting is IErrors { } /// @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 @@ -452,24 +486,19 @@ contract VoteWeighting 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) { @@ -487,17 +516,13 @@ contract VoteWeighting is IErrors { } // Allocate - nominees = new address[](numNominees); - chainIds = new uint256[](numNominees); + 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]; } } } \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index 9f334db..454ff71 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -14,7 +14,7 @@ const ALCHEMY_API_KEY_MAINNET = process.env.ALCHEMY_API_KEY_MAINNET; const ALCHEMY_API_KEY_MATIC = process.env.ALCHEMY_API_KEY_MATIC; const ALCHEMY_API_KEY_GOERLI = process.env.ALCHEMY_API_KEY_GOERLI; const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA; -const ALCHEMY_API_KEY_MUMBAI = process.env.ALCHEMY_API_KEY_MUMBAI; +const ALCHEMY_API_KEY_AMOY = process.env.ALCHEMY_API_KEY_AMOY; let TESTNET_MNEMONIC = process.env.TESTNET_MNEMONIC; const accounts = { @@ -88,8 +88,8 @@ module.exports = { accounts: accounts, chainId: 11155111, }, - polygonMumbai: { - url: "https://polygon-mumbai.g.alchemy.com/v2/" + ALCHEMY_API_KEY_MUMBAI, + polygonAmoy: { + url: "https://polygon-amoy.g.alchemy.com/v2/" + ALCHEMY_API_KEY_AMOY, accounts: accounts, }, chiado: { @@ -122,6 +122,14 @@ module.exports = { }, etherscan: { customChains: [ + { + network: "polygonAmoy", + chainId: 80002, + urls: { + apiURL: "https://api-amoy.polygonscan.com/api", + browserURL: "https://amoy.polygonscan.com/" + } + }, { network: "chiado", chainId: 10200, @@ -205,7 +213,7 @@ module.exports = { celo: CELOSCAN_API_KEY, goerli: ETHERSCAN_API_KEY, sepolia: ETHERSCAN_API_KEY, - polygonMumbai: POLYGONSCAN_API_KEY, + polygonAmoy: POLYGONSCAN_API_KEY, chiado: GNOSISSCAN_API_KEY, arbitrumSepolia: ARBISCAN_API_KEY, optimisticSepolia: OPSCAN_API_KEY, diff --git a/package.json b/package.json index b4f6697..22d9ab3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-etherscan": "^3.1.7", - "hardhat": "^2.22.2", + "hardhat": "^2.22.3", "@typechain/hardhat": "^9.1.0", "ethers": "^5.7.2", "@typechain/ethers-v5": "^11.1.2", diff --git a/yarn.lock b/yarn.lock index 3f7af79..f839951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -666,65 +666,53 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/edr-darwin-arm64@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.4.tgz#e5aac2b7726f44cffe120bdd7e25e1f120471591" - integrity sha512-tjavrUFLWnkn0PI+jk0D83hP2jjbmeXT1QLd5NtIleyGrJ00ZWVl+sfuA2Lle3kzfOceoI2VTR0n1pZB4KJGbQ== - -"@nomicfoundation/edr-darwin-x64@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.4.tgz#cbcc0a2dcda0a7c0a900a74efc6918cff134dc23" - integrity sha512-dXO0vlIoBosp8gf5/ah3dESMymjwit0Daef1E4Ew3gZ8q3LAdku0RC+YEQJi9f0I3QNfdgIrBTzibRZUoP+kVA== - -"@nomicfoundation/edr-linux-arm64-gnu@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.4.tgz#12073f97d310176bb24ad7d48c25128ea8eff093" - integrity sha512-dv38qmFUaqkkeeA9S0JjerqruytTfHav7gbPLpZUAEXPlJGo49R0+HQxd45I0msbm6NAXbkmKEchTLApp1ohaA== - -"@nomicfoundation/edr-linux-arm64-musl@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.4.tgz#c9bc685d4d14bf21d9c3e326edd44e009e24492d" - integrity sha512-CfEsb6gdCMVIlRSpWYTxoongEKHB60V6alE/y8mkfjIo7tA95wyiuvCtyo3fpiia3wQV7XoMYgIJHObHiKLKtA== - -"@nomicfoundation/edr-linux-x64-gnu@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.4.tgz#37486cbe317b8caf7961e500fc0150c45c895a56" - integrity sha512-V0CpJA2lYWulgTR+zP11ftBAEwkpMAAki/AuMu3vd7HoPfjwIDzWDQR5KFU17qFmqAVz0ICRxsxDlvvBZ/PUxA== - -"@nomicfoundation/edr-linux-x64-musl@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.4.tgz#399278807100a1833f6c8a39c17d5beaaf7a9223" - integrity sha512-0sgTrwZajarukerU/QSb+oRdlQLnJdd7of8OlXq2wtpeTNTqemgCOwY2l2qImbWboMpVrYgcmGbINXNVPCmuJw== - -"@nomicfoundation/edr-win32-arm64-msvc@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.4.tgz#879028e2708538fd54efc349c1a4de107a15abb4" - integrity sha512-bOl3vhMtV0W9ozUMF5AZRBWw1183hhhx+e1YJdDLMaqNkBUFYi2CZbMYefDylq2OKQtOQ0gPLhZvn+z2D21Ztw== - -"@nomicfoundation/edr-win32-ia32-msvc@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.4.tgz#97d54b8cfbbafa1cd2001bb115e583f1169bf9ae" - integrity sha512-yKQCpAX0uB2dalsSwOkau3yfNXkwBJa/Ks2OPl9AjHqJ+E8AqvBEB9jRpfQrdPzElMsgZuN4mqE+wh+JxY+0Aw== - -"@nomicfoundation/edr-win32-x64-msvc@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.4.tgz#abfc447eb6bd1a9be868bec5c9d14546398ab609" - integrity sha512-fResvsL/fSucep1K5W6iOs8lqqKKovHLsAmigMzAYVovqkyZKgCGVS/D8IVxA0nxuGCOlNxFnVmwWtph3pbKWA== - -"@nomicfoundation/edr@^0.3.1": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.4.tgz#e8eaf41963460139c47b0785f1a6a2a1c1b24ae0" - integrity sha512-e4jzVeJ+VTKBFzNgKDbSVnGVbHYNZHIfMdgifQBugXPiIa6QEUzZqleh2+y4lhkXcCthnFyrTYe3jiEpUzr3cA== +"@nomicfoundation/edr-darwin-arm64@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.5.tgz#3c428000ec27501617a00f7c447054a272f99177" + integrity sha512-gIXUIiPMUy6roLHpNlxf15DumU7/YhffUf7XIB+WUjMecaySfTGyZsTGnCMJZqrDyiYqWPyPKwCV/2u/jqFAUg== + +"@nomicfoundation/edr-darwin-x64@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.5.tgz#fee26c4a83c9fc534bc0a719e88382fafeed69fe" + integrity sha512-0MrpOCXUK8gmplpYZ2Cy0holHEylvWoNeecFcrP2WJ5DLQzrB23U5JU2MvUzOJ7aL76Za1VXNBWi/UeTWdHM+w== + +"@nomicfoundation/edr-linux-arm64-gnu@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.5.tgz#c48ad44b578abb50706cd8cad607a65b1289689f" + integrity sha512-aw9f7AZMiY1dZFNePJGKho2k+nEgFgzUAyyukiKfSqUIMXoFXMf1U3Ujv848czrSq9c5XGcdDa2xnEf3daU3xg== + +"@nomicfoundation/edr-linux-arm64-musl@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.5.tgz#7cc1b12e2adf872e5a542647182262cc4582f8d9" + integrity sha512-cVFRQjyABBlsbDj+XTczYBfrCHprZ6YNzN8gGGSqAh+UGIJkAIRomK6ar27GyJLNx3HkgbuDoi/9kA0zOo/95w== + +"@nomicfoundation/edr-linux-x64-gnu@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.5.tgz#ae4366c6fad03ea6ec3e2f2bafe397661c11f054" + integrity sha512-CjOg85DfR1Vt0fQWn5U0qi26DATK9tVzo3YOZEyI0JBsnqvk43fUTPv3uUAWBrPIRg5O5kOc9xG13hSpCBBxBg== + +"@nomicfoundation/edr-linux-x64-musl@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.5.tgz#4071929fcece90f95c271f6ba455c55f35750efd" + integrity sha512-hvX8bBGpBydAVevzK8jsu2FlqVZK1RrCyTX6wGHnltgMuBaoGLHYtNHiFpteOaJw2byYMiORc2bvj+98LhJ0Ew== + +"@nomicfoundation/edr-win32-x64-msvc@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.5.tgz#6e6684e56f336c67fbda9bf75b21d7ac586631b8" + integrity sha512-IJXjW13DY5UPsx/eG5DGfXtJ7Ydwrvw/BTZ2Y93lRLHzszVpSmeVmlxjZP5IW2afTSgMLaAAsqNw4NhppRGN8A== + +"@nomicfoundation/edr@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.5.tgz#04f980386f871e1c3bbc180b290d37af0336c81d" + integrity sha512-dPSM9DuI1sr71gqWUMgLo8MjHQWO4+WNDm3iWaT6P4vUFJReZX5qwA5X+3UwIPBry8GvNY084u7yWUvB3/8rqA== optionalDependencies: - "@nomicfoundation/edr-darwin-arm64" "0.3.4" - "@nomicfoundation/edr-darwin-x64" "0.3.4" - "@nomicfoundation/edr-linux-arm64-gnu" "0.3.4" - "@nomicfoundation/edr-linux-arm64-musl" "0.3.4" - "@nomicfoundation/edr-linux-x64-gnu" "0.3.4" - "@nomicfoundation/edr-linux-x64-musl" "0.3.4" - "@nomicfoundation/edr-win32-arm64-msvc" "0.3.4" - "@nomicfoundation/edr-win32-ia32-msvc" "0.3.4" - "@nomicfoundation/edr-win32-x64-msvc" "0.3.4" + "@nomicfoundation/edr-darwin-arm64" "0.3.5" + "@nomicfoundation/edr-darwin-x64" "0.3.5" + "@nomicfoundation/edr-linux-arm64-gnu" "0.3.5" + "@nomicfoundation/edr-linux-arm64-musl" "0.3.5" + "@nomicfoundation/edr-linux-x64-gnu" "0.3.5" + "@nomicfoundation/edr-linux-x64-musl" "0.3.5" + "@nomicfoundation/edr-win32-x64-msvc" "0.3.5" "@nomicfoundation/ethereumjs-common@4.0.4": version "4.0.4" @@ -3177,14 +3165,14 @@ hardhat-tracer@^2.8.1: debug "^4.3.4" ethers "^5.6.1" -hardhat@^2.22.2: - version "2.22.2" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.2.tgz#0cadd7ec93bf39bab09f81603e75bc5e92acea3d" - integrity sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw== +hardhat@^2.22.3: + version "2.22.3" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.3.tgz#50605daca6b29862397e446c42ec14c89430bec3" + integrity sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/edr" "^0.3.1" + "@nomicfoundation/edr" "^0.3.5" "@nomicfoundation/ethereumjs-common" "4.0.4" "@nomicfoundation/ethereumjs-tx" "5.0.4" "@nomicfoundation/ethereumjs-util" "9.0.4" From 7b7e0238859a2ee151e16882eeb5becd1271dc31 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 26 Apr 2024 10:57:45 +0100 Subject: [PATCH 2/3] test: adjusting tests back to 100% coverage --- contracts/VoteWeighting.sol | 2 +- test/VoteWeighting.js | 94 ++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index 08099dd..21f8d18 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -220,7 +220,7 @@ contract VoteWeighting is IErrors { /// @dev Add EVM nominee address along with the chain Id. /// @param account Address of the nominee. /// @param chainId Chain Id. - function addNominee(address account, uint256 chainId) external { + function addNomineeEVM(address account, uint256 chainId) external { // Check for the zero address if (account == address(0)) { revert ZeroAddress(); diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index 4973d70..e5b8614 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -18,12 +18,21 @@ describe("Voting Escrow OLAS", function () { const E18 = 10**18; const oneOLASBalance = ethers.utils.parseEther("1"); const AddressZero = ethers.constants.AddressZero; + const HashZero = ethers.constants.HashZero; const maxU256 = ethers.constants.MaxUint256; function getNextTime(ts) { return Math.floor((ts + oneWeek) / oneWeek) * oneWeek; } + function convertAddressToBytes32(account) { + return ("0x" + "0".repeat(24) + account.slice(2)).toLowerCase(); + } + + function convertBytes32ToAddress(account) { + return "0x" + account.slice(26); + } + beforeEach(async function () { const OLAS = await ethers.getContractFactory("OLAS"); olas = await OLAS.deploy(); @@ -59,39 +68,52 @@ describe("Voting Escrow OLAS", function () { context("Adding nominees", async function () { it("Should fail with wrong nominee params", async function () { + let nominee = signers[1].address; // Lock one OLAS into veOLAS await olas.approve(ve.address, oneOLASBalance); await ve.createLock(oneOLASBalance, oneYear); // Try to add a zero address nominee await expect( - vw.addNominee(AddressZero, chainId) + vw.addNomineeEVM(AddressZero, chainId) ).to.be.revertedWithCustomError(vw, "ZeroAddress"); // Try to add a zero chain Id await expect( - vw.addNominee(signers[1].address, 0) + vw.addNomineeEVM(nominee, 0) ).to.be.revertedWithCustomError(vw, "ZeroValue"); // Try to add an overflow chain Id - let overflowChainId = await vw.MAX_CHAIN_ID(); - overflowChainId = overflowChainId.add(1); + const maxEVMChainId = await vw.MAX_EVM_CHAIN_ID(); + const overflowChainId = maxEVMChainId.add(1); await expect( - vw.addNominee(signers[1].address, overflowChainId) + vw.addNomineeEVM(nominee, overflowChainId) ).to.be.revertedWithCustomError(vw, "Overflow"); + + // Try to add an underflow chain Id for the non-EVM chain + nominee = convertAddressToBytes32(nominee); + await expect( + vw.addNomineeNonEVM(nominee, maxEVMChainId) + ).to.be.revertedWithCustomError(vw, "Underflow"); + + // Try to add a non-EVM nominee with a zero address + await expect( + vw.addNomineeNonEVM(HashZero, chainId) + ).to.be.revertedWithCustomError(vw, "ZeroAddress"); }); it("Add nominee", async function () { + let nominee = signers[1].address; // Lock one OLAS into veOLAS await olas.approve(ve.address, oneOLASBalance); await ve.createLock(oneOLASBalance, oneYear); // Add a nominee - await vw.addNominee(signers[1].address, chainId); + await vw.addNomineeEVM(nominee, chainId); // Try to add the same nominee await expect( - vw.addNominee(signers[1].address, chainId) + vw.addNomineeEVM(nominee, chainId) ).to.be.revertedWithCustomError(vw, "NomineeAlreadyExists"); // Check the nominee setup @@ -99,24 +121,30 @@ describe("Voting Escrow OLAS", function () { expect(numNominees).to.equal(1); const nomineeChainId = await vw.getNominee(1); - expect(nomineeChainId.nominee).to.equal(signers[1].address); + expect(nomineeChainId.account).to.equal(convertAddressToBytes32(nominee)); expect(nomineeChainId.chainId).to.equal(chainId); const nomineeChainIds = await vw.getNominees(1, 1); - expect(nomineeChainIds.nominees[0]).to.equal(signers[1].address); - expect(nomineeChainIds.chainIds[0]).to.equal(chainId); + expect(nomineeChainIds[0].account).to.equal(convertAddressToBytes32(nominee)); + expect(nomineeChainIds[0].chainId).to.equal(chainId); - let nomineeId = await vw.getNomineeId(signers[1].address, chainId); + let nomineeId = await vw.getNomineeId(convertAddressToBytes32(nominee), chainId); expect(nomineeId).to.equal(1); // Check the nominee Id of a nonexistent nominee - nomineeId = await vw.getNomineeId(signers[1].address, chainId + 1); + nomineeId = await vw.getNomineeId(convertAddressToBytes32(nominee), chainId + 1); expect(nomineeId).to.equal(0); + + // Adding a non-EVM nominee + nominee = convertAddressToBytes32(nominee); + const maxEVMChainId = await vw.MAX_EVM_CHAIN_ID(); + await vw.addNomineeNonEVM(nominee, maxEVMChainId.add(1)); }); it("Get nominees", async function () { + const nominee = signers[1].address; // Add a nominee - await vw.addNominee(signers[1].address, chainId); + await vw.addNomineeEVM(nominee, chainId); // Try to get the zero-th nominees await expect( @@ -141,7 +169,7 @@ describe("Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(vw, "Overflow"); // Add one more nominee - await vw.addNominee(signers[1].address, chainId + 1); + await vw.addNomineeEVM(nominee, chainId + 1); // Try to get the nonexistent nominee await expect( vw.getNominee(3) @@ -159,7 +187,8 @@ describe("Voting Escrow OLAS", function () { it("Should fail with wrong input arguments", async function () { // Add a nominee let nominee = signers[1].address; - await vw.addNominee(nominee, chainId); + await vw.addNomineeEVM(nominee, chainId); + nominee = convertAddressToBytes32(nominee); // Approve OLAS for veOLAS await olas.approve(ve.address, oneOLASBalance); @@ -196,7 +225,9 @@ describe("Voting Escrow OLAS", function () { // Try to vote for another nominee with all the voting power used nominee = signers[2].address; - await vw.addNominee(nominee, chainId); + await vw.addNomineeEVM(nominee, chainId); + nominee = convertAddressToBytes32(nominee); + await expect( vw.voteForNomineeWeights(nominee, chainId, 1) ).to.be.revertedWithCustomError(vw, "Overflow"); @@ -230,8 +261,9 @@ describe("Voting Escrow OLAS", function () { await ve.createLock(oneOLASBalance, oneYear); // Add a nominee - const nominee = signers[1].address; - await vw.addNominee(nominee, chainId); + let nominee = signers[1].address; + await vw.addNomineeEVM(nominee, chainId); + nominee = convertAddressToBytes32(nominee); // Get the next point timestamp where votes are written after voting const block = await ethers.provider.getBlock("latest"); @@ -248,8 +280,9 @@ describe("Voting Escrow OLAS", function () { // Add one more nominee - const nominee2 = signers[2].address; - await vw.addNominee(nominee2, chainId); + let nominee2 = signers[2].address; + await vw.addNomineeEVM(nominee2, chainId); + nominee2 = convertAddressToBytes32(nominee2); // Make sure the initial weight is zero weight = await vw.nomineeRelativeWeight(nominee2, chainId, nextTime); @@ -290,8 +323,9 @@ describe("Voting Escrow OLAS", function () { await ve.createLock(oneOLASBalance, oneYear); // Add a nominee - const nominee = signers[1].address; - await vw.addNominee(nominee, chainId); + let nominee = signers[1].address; + await vw.addNomineeEVM(nominee, chainId); + nominee = convertAddressToBytes32(nominee); // Wait for several weeks await helpers.time.increase(oneWeek * 3); @@ -318,12 +352,14 @@ describe("Voting Escrow OLAS", function () { // Add nominees const numNominees = 2; - const nominees = [signers[1].address, signers[2].address]; + let nominees = [signers[1].address, signers[2].address]; const chainIds = new Array(numNominees).fill(chainId); for (let i = 0; i < numNominees; i++) { - await vw.addNominee(nominees[i], chainIds[i]); + await vw.addNomineeEVM(nominees[i], chainIds[i]); } + nominees = [convertAddressToBytes32(nominees[0]), convertAddressToBytes32(nominees[1])]; + // Get the next point timestamp where votes are written after voting const block = await ethers.provider.getBlock("latest"); const nextTime = getNextTime(block.timestamp); @@ -348,8 +384,9 @@ describe("Voting Escrow OLAS", function () { await ve.createLock(oneOLASBalance, oneYear); // Add a nominee - const nominee = signers[1].address; - await vw.addNominee(nominee, chainId); + let nominee = signers[1].address; + await vw.addNomineeEVM(nominee, chainId); + nominee = convertAddressToBytes32(nominee); // Vote for the nominee await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight); @@ -378,10 +415,11 @@ describe("Voting Escrow OLAS", function () { // Add nominees const numNominees = 2; - const nominees = [signers[2].address, signers[3].address]; + let nominees = [signers[2].address, signers[3].address]; for (let i = 0; i < numNominees; i++) { - await vw.addNominee(nominees[i], chainId); + await vw.addNomineeEVM(nominees[i], chainId); } + nominees = [convertAddressToBytes32(nominees[0]), convertAddressToBytes32(nominees[1])]; // Lock one OLAS into veOLAS by deployer and another account await olas.approve(ve.address, oneOLASBalance); From be3583c445899cfa9ef03933f607e942de470311 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 26 Apr 2024 11:05:04 +0100 Subject: [PATCH 3/3] chore: solhint --- contracts/VoteWeighting.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index 21f8d18..aba20e2 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import "./interfaces/IErrors.sol"; +import {IErrors} from "./interfaces/IErrors.sol"; interface IVEOLAS { // Structure for voting escrow points