From 4d7672ea50c873c1175bf46dd914194201718f92 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Wed, 27 Mar 2024 19:14:47 +0000 Subject: [PATCH 01/14] feat: wormhole l1 to l2 to l1 messaging experiments --- contracts/bridges/test/WormholeL1Receiver.sol | 27 ++++ contracts/bridges/test/WormholeL1Sender.sol | 72 +++++++++++ .../test/WormholeL2ReceiverL1Sender.sol | 115 ++++++++++++++++++ .../deploy_03_womholel2receiverl1sender.js | 58 +++++++++ .../l1_l2_l1/deploy_04_womholel1receiver.js | 57 +++++++++ .../l1_l2_l1/deploy_05_womholel1sender.js | 58 +++++++++ .../l1_l2_l1/globals_optimistic_sepolia.json | 1 + ...senger_l2_only_sepolia_optimism_sepolia.js | 60 +++++++++ .../messenger_sepolia_optimism_sepolia.js | 81 ++++++++++++ .../verify_03_womholel2receiverl1sender.js | 9 ++ .../l1_l2_l1/verify_05_womholel1sender.js | 10 ++ 11 files changed, 548 insertions(+) create mode 100644 contracts/bridges/test/WormholeL1Receiver.sol create mode 100644 contracts/bridges/test/WormholeL1Sender.sol create mode 100644 contracts/bridges/test/WormholeL2ReceiverL1Sender.sol create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/globals_optimistic_sepolia.json create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_sepolia_optimism_sepolia.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js create mode 100644 scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js diff --git a/contracts/bridges/test/WormholeL1Receiver.sol b/contracts/bridges/test/WormholeL1Receiver.sol new file mode 100644 index 0000000..dee8a10 --- /dev/null +++ b/contracts/bridges/test/WormholeL1Receiver.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @title WormholeL1Receiver - Smart contract for the L1 message receiving via wormhole +/// @author Aleksandr Kuperman - +/// @author Andrey Lebedev - +/// @author Mariapia Moscatiello - +contract WormholeL1Receiver { + event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain); + + /// @dev Processes a message received from L1 Wormhole Relayer contract. + /// @notice The sender must be the source contract address. + /// @param data Bytes message sent from L1 Wormhole Relayer contract. + /// @param sourceAddress The (wormhole format) address on the sending chain which requested this delivery. + /// @param sourceChain The wormhole chain Id where this delivery was requested. + /// @param deliveryHash The VAA hash of the deliveryVAA. + function receiveWormholeMessages( + bytes memory data, + bytes[] memory, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + // Emit received message + emit MessageReceived(sourceAddress, data, deliveryHash, sourceChain); + } +} \ No newline at end of file diff --git a/contracts/bridges/test/WormholeL1Sender.sol b/contracts/bridges/test/WormholeL1Sender.sol new file mode 100644 index 0000000..4fba88e --- /dev/null +++ b/contracts/bridges/test/WormholeL1Sender.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {IBridgeErrors} from "../../interfaces/IBridgeErrors.sol"; + +interface IWormhole { + function sendPayloadToEvm( + // Chain ID in Wormhole format + uint16 targetChain, + // Contract Address on target chain we're sending a message to + address targetAddress, + // The payload, encoded as bytes + bytes memory payload, + // How much value to attach to the delivery transaction + uint256 receiverValue, + // The gas limit to set on the delivery transaction + uint256 gasLimit + ) external payable returns ( + // Unique, incrementing ID, used to identify a message + uint64 sequence + ); +} + +/// @title WormholeL1Sender - Smart contract for sending a message from L2 to L1 via wormhole +/// @author Aleksandr Kuperman - +/// @author Andrey Lebedev - +/// @author Mariapia Moscatiello - +contract WormholeL1Sender is IBridgeErrors { + event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain); + + uint256 public constant GAS_LIMIT = 50_000; + // L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network + address public immutable wormholeRelayer; + // Source chain Id + uint16 public immutable sourceChainId; + // Source contract to communicate with on L1 + address public sourceSender; + + /// @dev WormholeMessenger constructor. + /// @param _wormholeRelayer L2 Wormhole Relayer address. + /// @param _sourceChainId Source wormhole format chain Id. + constructor(address _wormholeRelayer, uint16 _sourceChainId, address _sourceSender) { + // Check for zero addresses + if (_wormholeRelayer == address(0) || _sourceSender == address(0)) { + revert ZeroAddress(); + } + + // Check source chain Id + if (_sourceChainId == 0) { + revert ZeroValue(); + } + + wormholeRelayer = _wormholeRelayer; + sourceChainId = _sourceChainId; + sourceSender = _sourceSender; + } + + function sendMessage() external payable { + bytes32 message = keccak256(abi.encode("Hello")); + + // Send the message + IWormhole(wormholeRelayer).sendPayloadToEvm{value: msg.value}( + sourceChainId, + sourceSender, + abi.encode(message), + 0, + GAS_LIMIT + ); + } + + receive() external payable {} +} \ No newline at end of file diff --git a/contracts/bridges/test/WormholeL2ReceiverL1Sender.sol b/contracts/bridges/test/WormholeL2ReceiverL1Sender.sol new file mode 100644 index 0000000..ccc9241 --- /dev/null +++ b/contracts/bridges/test/WormholeL2ReceiverL1Sender.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {IBridgeErrors} from "../../interfaces/IBridgeErrors.sol"; + +interface IWormhole { + function quoteEVMDeliveryPrice( + uint16 targetChain, + uint256 receiverValue, + uint256 gasLimit + ) external returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused); + + function sendPayloadToEvm( + // Chain ID in Wormhole format + uint16 targetChain, + // Contract Address on target chain we're sending a message to + address targetAddress, + // The payload, encoded as bytes + bytes memory payload, + // How much value to attach to the delivery transaction + uint256 receiverValue, + // The gas limit to set on the delivery transaction + uint256 gasLimit + ) external payable returns ( + // Unique, incrementing ID, used to identify a message + uint64 sequence + ); +} + +/// @title WormholeL2ReceiverL1Sender - Smart contract for the L1-L2-L1 message relaying via wormhole +/// @author Aleksandr Kuperman - +/// @author Andrey Lebedev - +/// @author Mariapia Moscatiello - +contract WormholeL2ReceiverL1Sender is IBridgeErrors { + event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain); + + uint256 public constant GAS_LIMIT = 50_000; + // L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network + address public immutable wormholeRelayer; + // Source chain Id + uint16 public immutable sourceChainId; + // Source contract to communicate with on L1 + address public sourceSender; + // Delivery hashes + mapping(bytes32 => bool) public mapDeliveryHashes; + + /// @dev WormholeMessenger constructor. + /// @param _wormholeRelayer L2 Wormhole Relayer address. + /// @param _sourceChainId Source wormhole format chain Id. + constructor(address _wormholeRelayer, uint16 _sourceChainId) { + // Check for zero addresses + if (_wormholeRelayer == address(0)) { + revert ZeroAddress(); + } + + // Check source chain Id + if (_sourceChainId == 0) { + revert ZeroValue(); + } + + wormholeRelayer = _wormholeRelayer; + sourceChainId = _sourceChainId; + } + + + /// @dev Processes a message received from L2 Wormhole Relayer contract. + /// @notice The sender must be the source contract address. + /// @param data Bytes message sent from L2 Wormhole Relayer contract. + /// @param sourceAddress The (wormhole format) address on the sending chain which requested this delivery. + /// @param sourceChain The wormhole chain Id where this delivery was requested. + /// @param deliveryHash The VAA hash of the deliveryVAA. + function receiveWormholeMessages( + bytes memory data, + bytes[] memory, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + // Check L2 Wormhole Relayer address + if (msg.sender != wormholeRelayer) { + revert TargetRelayerOnly(msg.sender, wormholeRelayer); + } + + // Check the source chain Id + if (sourceChain != sourceChainId) { + revert WrongSourceChainId(sourceChain, sourceChainId); + } + + // Check the delivery hash uniqueness + if (mapDeliveryHashes[deliveryHash]) { + revert AlreadyDelivered(deliveryHash); + } + mapDeliveryHashes[deliveryHash] = true; + + sourceSender = abi.decode(data, (address)); + + // Get a quote for the cost of gas for delivery + uint256 cost; + (cost, ) = IWormhole(wormholeRelayer).quoteEVMDeliveryPrice(sourceChain, 0, GAS_LIMIT); + + // Send the message + IWormhole(wormholeRelayer).sendPayloadToEvm{value: cost}( + sourceChain, + sourceSender, + abi.encode(keccak256("Hello")), + 0, + GAS_LIMIT + ); + + // Emit received message + emit MessageReceived(sourceAddress, data, deliveryHash, sourceChain); + } + + receive() external payable {} +} \ No newline at end of file diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js new file mode 100644 index 0000000..81460f8 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js @@ -0,0 +1,58 @@ +/*global process*/ + +const { ethers } = require("hardhat"); +const { LedgerSigner } = require("@anders-t/ethers-ledger"); + +async function main() { + const fs = require("fs"); + const globalsFile = "globals.json"; + const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); + let parsedData = JSON.parse(dataFromJSON); + const useLedger = parsedData.useLedger; + const derivationPath = parsedData.derivationPath; + const providerName = parsedData.providerName; + const gasPriceInGwei = parsedData.gasPriceInGwei; + let EOA; + + const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL); + const signers = await ethers.getSigners(); + + if (useLedger) { + EOA = new LedgerSigner(provider, derivationPath); + } else { + EOA = signers[0]; + } + // EOA address + const deployer = await EOA.getAddress(); + console.log("EOA is:", deployer); + + // Transaction signing and execution + console.log("1. EOA to deploy WormholeL2ReceiverL1Sender contract"); + const WormholeL2ReceiverL1Sender = await ethers.getContractFactory("WormholeL2ReceiverL1Sender"); + console.log("You are signing the following transaction: WormholeL2ReceiverL1Sender.connect(EOA).deploy()"); + const wormholeL2ReceiverL1Sender = await WormholeL2ReceiverL1Sender.connect(EOA).deploy(parsedData.L2WormholeRelayerAddress, + parsedData.sourceChainId, parsedData.wormholeL1ReceiverAddress); + const result = await wormholeL2ReceiverL1Sender.deployed(); + + // Transaction details + console.log("Contract deployment: WormholeL2ReceiverL1Sender"); + console.log("Contract address:", wormholeL2ReceiverL1Sender.address); + console.log("Transaction:", result.deployTransaction.hash); + + // Writing updated parameters back to the JSON file + parsedData.wormholeL2ReceiverL1SenderAddress = wormholeL2ReceiverL1Sender.address; + fs.writeFileSync(globalsFile, JSON.stringify(parsedData)); + + // Contract verification + if (parsedData.contractVerification) { + const execSync = require("child_process").execSync; + execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js --network " + providerName + " " + wormholeL2ReceiverL1Sender.address, { encoding: "utf-8" }); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js new file mode 100644 index 0000000..523d560 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js @@ -0,0 +1,57 @@ +/*global process*/ + +const { ethers } = require("hardhat"); +const { LedgerSigner } = require("@anders-t/ethers-ledger"); + +async function main() { + const fs = require("fs"); + const globalsFile = "globals.json"; + const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); + let parsedData = JSON.parse(dataFromJSON); + const useLedger = parsedData.useLedger; + const derivationPath = parsedData.derivationPath; + const providerName = "sepolia"; + const gasPriceInGwei = parsedData.gasPriceInGwei; + let EOA; + + const provider = await ethers.providers.getDefaultProvider(providerName); + const signers = await ethers.getSigners(); + + if (useLedger) { + EOA = new LedgerSigner(provider, derivationPath); + } else { + EOA = signers[0]; + } + // EOA address + const deployer = await EOA.getAddress(); + console.log("EOA is:", deployer); + + // Transaction signing and execution + console.log("1. EOA to deploy WormholeL1Receiver contract"); + const WormholeL1Receiver = await ethers.getContractFactory("WormholeL1Receiver"); + console.log("You are signing the following transaction: WormholeL1Receiver.connect(EOA).deploy()"); + const wormholeL1Receiver = await WormholeL1Receiver.connect(EOA).deploy(); + const result = await wormholeL1Receiver.deployed(); + + // Transaction details + console.log("Contract deployment: WormholeL1Receiver"); + console.log("Contract address:", wormholeL1Receiver.address); + console.log("Transaction:", result.deployTransaction.hash); + + // Writing updated parameters back to the JSON file + parsedData.wormholeL1ReceiverAddress = wormholeL1Receiver.address; + fs.writeFileSync(globalsFile, JSON.stringify(parsedData)); + + // Contract verification + if (parsedData.contractVerification) { + const execSync = require("child_process").execSync; + execSync("npx hardhat verify --network " + providerName + " " + wormholeL1Receiver.address, { encoding: "utf-8" }); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js new file mode 100644 index 0000000..617f615 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js @@ -0,0 +1,58 @@ +/*global process*/ + +const { ethers } = require("hardhat"); +const { LedgerSigner } = require("@anders-t/ethers-ledger"); + +async function main() { + const fs = require("fs"); + const globalsFile = "globals.json"; + const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); + let parsedData = JSON.parse(dataFromJSON); + const useLedger = parsedData.useLedger; + const derivationPath = parsedData.derivationPath; + const providerName = parsedData.providerName; + const gasPriceInGwei = parsedData.gasPriceInGwei; + let EOA; + + const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL); + const signers = await ethers.getSigners(); + + if (useLedger) { + EOA = new LedgerSigner(provider, derivationPath); + } else { + EOA = signers[0]; + } + // EOA address + const deployer = await EOA.getAddress(); + console.log("EOA is:", deployer); + + // Transaction signing and execution + console.log("1. EOA to deploy WormholeL1Sender contract"); + const WormholeL1Sender = await ethers.getContractFactory("WormholeL1Sender"); + console.log("You are signing the following transaction: WormholeL1Sender.connect(EOA).deploy()"); + const wormholeL1Sender = await WormholeL1Sender.connect(EOA).deploy(parsedData.L2WormholeRelayerAddress, + parsedData.sourceChainId, parsedData.wormholeL1ReceiverAddress); + const result = await wormholeL1Sender.deployed(); + + // Transaction details + console.log("Contract deployment: WormholeL1Sender"); + console.log("Contract address:", wormholeL1Sender.address); + console.log("Transaction:", result.deployTransaction.hash); + + // Writing updated parameters back to the JSON file + parsedData.wormholeL1SenderAddress = wormholeL1Sender.address; + fs.writeFileSync(globalsFile, JSON.stringify(parsedData)); + + // Contract verification + if (parsedData.contractVerification) { + const execSync = require("child_process").execSync; + execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js --network " + providerName + " " + wormholeL1Sender.address, { encoding: "utf-8" }); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/globals_optimistic_sepolia.json b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/globals_optimistic_sepolia.json new file mode 100644 index 0000000..0c8551c --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/globals_optimistic_sepolia.json @@ -0,0 +1 @@ +{"contractVerification":true,"useLedger":false,"derivationPath":"m/44'/60'/2'/0/0","providerName":"optimisticSepolia","gasPriceInGwei":"2","networkURL":"https://sepolia.optimism.io","sourceChainId":"10002","L1WormholeRelayerAddress":"0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470","L2WormholeRelayerAddress":"0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE","wormholeL2ReceiverL1SenderAddress":"0x1d333b46dB6e8FFd271b6C2D2B254868BD9A2dbd","wormholeL1ReceiverAddress":"0xF66E23209074FA7946E41f45f43d765281af2207","wormholeL1SenderAddress":"0x04A0afD079F14D539B17253Ea93563934A024165"} \ No newline at end of file diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js new file mode 100644 index 0000000..5cb3697 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js @@ -0,0 +1,60 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const optimisticSepoliaURL = "https://sepolia.optimism.io"; + const optimisticSepoliaProvider = new ethers.providers.JsonRpcProvider(optimisticSepoliaURL); + await optimisticSepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number optimisticSepolia: " + result); + }); + + const fs = require("fs"); + // WormholeRelayer address on optimistic sepolia + const wormholeRelayerAddress = "0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE"; + const wormholeRelayerJSON = "abis/test/WormholeRelayer.json"; + let contractFromJSON = fs.readFileSync(wormholeRelayerJSON, "utf8"); + const wormholeRelayerABI = JSON.parse(contractFromJSON); + const wormholeRelayer = new ethers.Contract(wormholeRelayerAddress, wormholeRelayerABI, optimisticSepoliaProvider); + + // Test deployed WormholeMessenger address on optimisticSepolia + const wormholeMessengerAddress = "0x04A0afD079F14D539B17253Ea93563934A024165"; // payable process on L2 + const wormholeMessengerJSON = "artifacts/contracts/bridges/test/WormholeL2ReceiverL1Sender.sol/WormholeL2ReceiverL1Sender.json"; + contractFromJSON = fs.readFileSync(wormholeMessengerJSON, "utf8"); + let parsedFile = JSON.parse(contractFromJSON); + const wormholeMessengerABI = parsedFile["abi"]; + const wormholeMessenger = new ethers.Contract(wormholeMessengerAddress, wormholeMessengerABI, optimisticSepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + const EOAoptimisticSepolia = new ethers.Wallet(account, optimisticSepoliaProvider); + console.log("EOA address",EOAsepolia.address); + if (EOAoptimisticSepolia.address == EOAsepolia.address) { + console.log("Correct wallet setup"); + } + + const targetChain = 10002; // sepolia + const minGasLimit = "50000"; + const transferCost = await wormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256)"](targetChain, 0, minGasLimit); + + // Send the message to optimisticSepolia receiver + tx = await wormholeMessenger.connect(EOAoptimisticSepolia).sendMessage({ value: transferCost.nativePriceQuote }); + console.log("Execution hash", tx.hash); + await tx.wait(); + // https://wormholescan.io/#/tx/0xef934da740738881b3069373602a64148944e15ae3b5da2c2630f85886ae6453?network=TESTNET + // https://sepolia.etherscan.io/tx/0x3c85e931ee7f974f771d2b589a11f24e6982f4d68444e47ff57f6a24203788b0 +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_sepolia_optimism_sepolia.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_sepolia_optimism_sepolia.js new file mode 100644 index 0000000..f13da7d --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_sepolia_optimism_sepolia.js @@ -0,0 +1,81 @@ +/*global process*/ + +const { ethers } = require("ethers"); + +async function main() { + const sepoliaURL = "https://eth-sepolia.g.alchemy.com/v2/" + process.env.ALCHEMY_API_KEY_SEPOLIA; + const sepoliaProvider = new ethers.providers.JsonRpcProvider(sepoliaURL); + await sepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number sepolia: " + result); + }); + + const optimisticSepoliaURL = "https://sepolia.optimism.io"; + const optimisticSepoliaProvider = new ethers.providers.JsonRpcProvider(optimisticSepoliaURL); + await optimisticSepoliaProvider.getBlockNumber().then((result) => { + console.log("Current block number optimisticSepolia: " + result); + }); + + const fs = require("fs"); + // WormholeRelayer address on sepolia + const wormholeRelayerAddress = "0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470"; + const wormholeRelayerJSON = "abis/test/WormholeRelayer.json"; + let contractFromJSON = fs.readFileSync(wormholeRelayerJSON, "utf8"); + const wormholeRelayerABI = JSON.parse(contractFromJSON); + const wormholeRelayer = new ethers.Contract(wormholeRelayerAddress, wormholeRelayerABI, sepoliaProvider); + + // Test deployed WormholeMessenger address on optimisticSepolia + const wormholeMessengerAddress = "0x1d333b46dB6e8FFd271b6C2D2B254868BD9A2dbd"; // payable process on L2 + const wormholeMessengerJSON = "artifacts/contracts/bridges/test/WormholeL2ReceiverL1Sender.sol/WormholeL2ReceiverL1Sender.json"; + contractFromJSON = fs.readFileSync(wormholeMessengerJSON, "utf8"); + let parsedFile = JSON.parse(contractFromJSON); + const wormholeMessengerABI = parsedFile["abi"]; + const wormholeMessenger = new ethers.Contract(wormholeMessengerAddress, wormholeMessengerABI, optimisticSepoliaProvider); + + // Mock Timelock contract address on sepolia (has WormholeRelayer address in it already) + const mockTimelockAddress = "0x14CF2e543AB75B321bcf84C3AcC88d570Ccf9106"; // payable + const mockTimelockJSON = "artifacts/contracts/bridges/test/MockTimelock.sol/MockTimelock.json"; + contractFromJSON = fs.readFileSync(mockTimelockJSON, "utf8"); + parsedFile = JSON.parse(contractFromJSON); + const mockTimelockABI = parsedFile["abi"]; + const mockTimelock = new ethers.Contract(mockTimelockAddress, mockTimelockABI, sepoliaProvider); + + // Get the EOA + const account = ethers.utils.HDNode.fromMnemonic(process.env.TESTNET_MNEMONIC).derivePath("m/44'/60'/0'/0/0"); + const EOAsepolia = new ethers.Wallet(account, sepoliaProvider); + const EOAoptimisticSepolia = new ethers.Wallet(account, optimisticSepoliaProvider); + console.log("EOA address",EOAsepolia.address); + if (EOAoptimisticSepolia.address == EOAsepolia.address) { + console.log("Correct wallet setup"); + } + + const amountToSend = ethers.utils.parseEther("0.02"); + let tx = await EOAoptimisticSepolia.sendTransaction({to: wormholeMessenger.address, value: amountToSend}); + console.log("Send Optimistic hash", tx.hash); + await tx.wait(); + + const targetChain = 10005; // optimistic sepolia + const minGasLimit = "2000000"; + const transferCost = await wormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256)"](targetChain, 0, minGasLimit); + + // Data = WormholeL1Receiver address in the bytes32 format + const data = "0x000000000000000000000000f66e23209074fa7946e41f45f43d765281af2207"; + + // Build the final payload to be passed from the imaginary Timelock + const sendPayloadSelector = "0x4b5ca6f4"; + const timelockPayload = await wormholeRelayer.interface.encodeFunctionData(sendPayloadSelector, [targetChain, + wormholeMessengerAddress, data, 0, minGasLimit, targetChain, wormholeMessengerAddress]); + + // Send the message to optimisticSepolia receiver + tx = await mockTimelock.connect(EOAsepolia).execute(timelockPayload, { value: transferCost.nativePriceQuote }); + console.log("Timelock data execution hash", tx.hash); + await tx.wait(); + // https://wormholescan.io/#/tx/0x2a7daee399681fd37e066c2672e06f1c43ff3dbdad4c8cbbc941a00529e7ae10?network=TESTNET + // https://sepolia-optimism.etherscan.io/tx/0x31395dfd37e4095177e0d5a8ebbff0ebdcf30e8ee7e617078cf8677cec8ed24c +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js new file mode 100644 index 0000000..0121e59 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js @@ -0,0 +1,9 @@ +const fs = require("fs"); +const globalsFile = "globals.json"; +const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); +const parsedData = JSON.parse(dataFromJSON); + +module.exports = [ + parsedData.L2WormholeRelayerAddress, + parsedData.sourceChainId +]; \ No newline at end of file diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js new file mode 100644 index 0000000..7901c95 --- /dev/null +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js @@ -0,0 +1,10 @@ +const fs = require("fs"); +const globalsFile = "globals.json"; +const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); +const parsedData = JSON.parse(dataFromJSON); + +module.exports = [ + parsedData.L2WormholeRelayerAddress, + parsedData.sourceChainId, + parsedData.wormholeL1ReceiverAddress +]; \ No newline at end of file From 64ae08581b1b4c15e8c984be2d29f0ed1e746c36 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 10:08:25 +0100 Subject: [PATCH 02/14] chore: linter fix --- .../gnosis/test/mediator_goerli_chiado_change_governor.js | 8 ++++---- .../messenger_l2_only_sepolia_optimism_sepolia.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/deployment/bridges/gnosis/test/mediator_goerli_chiado_change_governor.js b/scripts/deployment/bridges/gnosis/test/mediator_goerli_chiado_change_governor.js index 48ed523..12daecc 100644 --- a/scripts/deployment/bridges/gnosis/test/mediator_goerli_chiado_change_governor.js +++ b/scripts/deployment/bridges/gnosis/test/mediator_goerli_chiado_change_governor.js @@ -52,10 +52,10 @@ async function main() { // Home Mediator to change the foreign governor address const rawPayload = homeMediator.interface.encodeFunctionData("changeForeignGovernor", [EOAchiado.address]); // Pack the second part of data - target = homeMediatorAddress; - value = 0; + const target = homeMediatorAddress; + const value = 0; const payload = ethers.utils.arrayify(rawPayload); - data = ethers.utils.solidityPack( + const data = ethers.utils.solidityPack( ["address", "uint96", "uint32", "bytes"], [target, value, payload.length, payload] ); @@ -67,7 +67,7 @@ async function main() { mediatorPayload, requestGasLimit]); // Send the message to chiado receiver - tx = await mockTimelock.connect(EOAgoerli).execute(timelockPayload); + const tx = await mockTimelock.connect(EOAgoerli).execute(timelockPayload); console.log("Timelock data execution hash", tx.hash); await tx.wait(); } diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js index 5cb3697..8763090 100644 --- a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/messenger_l2_only_sepolia_optimism_sepolia.js @@ -45,7 +45,7 @@ async function main() { const transferCost = await wormholeRelayer["quoteEVMDeliveryPrice(uint16,uint256,uint256)"](targetChain, 0, minGasLimit); // Send the message to optimisticSepolia receiver - tx = await wormholeMessenger.connect(EOAoptimisticSepolia).sendMessage({ value: transferCost.nativePriceQuote }); + const tx = await wormholeMessenger.connect(EOAoptimisticSepolia).sendMessage({ value: transferCost.nativePriceQuote }); console.log("Execution hash", tx.hash); await tx.wait(); // https://wormholescan.io/#/tx/0xef934da740738881b3069373602a64148944e15ae3b5da2c2630f85886ae6453?network=TESTNET From 3b2559087ba7308cf9dc1b92a4b8f2aadcf2043a Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 10:10:04 +0100 Subject: [PATCH 03/14] chore: linter fix --- .../bridges/optimistic/test/deploy_00_mock_timelock.js | 1 - .../bridges/polygon/test/fx_goerli_mumbai_hello_world.js | 8 -------- 2 files changed, 9 deletions(-) diff --git a/scripts/deployment/bridges/optimistic/test/deploy_00_mock_timelock.js b/scripts/deployment/bridges/optimistic/test/deploy_00_mock_timelock.js index 73515f0..9d15c68 100644 --- a/scripts/deployment/bridges/optimistic/test/deploy_00_mock_timelock.js +++ b/scripts/deployment/bridges/optimistic/test/deploy_00_mock_timelock.js @@ -11,7 +11,6 @@ async function main() { const useLedger = parsedData.useLedger; const derivationPath = parsedData.derivationPath; const providerName = "sepolia"; - const gasPriceInGwei = parsedData.gasPriceInGwei; let EOA; const provider = await ethers.providers.getDefaultProvider(providerName); diff --git a/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js b/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js index 1443d33..568a47b 100644 --- a/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js +++ b/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js @@ -26,14 +26,6 @@ async function main() { const fxRootABI = parsedFile["abi"]; const fxRoot = new ethers.Contract(fxRootAddress, fxRootABI, goerliProvider); - // FxChild address on mumbai - const fxChildAddress = "0xCf73231F28B7331BBe3124B907840A94851f9f11"; - const fxChildJSON = "artifacts/fx-portal/contracts/FxChild.sol/FxChild.json"; - contractFromJSON = fs.readFileSync(fxChildJSON, "utf8"); - parsedFile = JSON.parse(contractFromJSON); - const fxChildABI = parsedFile["abi"]; - const fxChild = new ethers.Contract(fxChildAddress, fxChildABI, mumbaiProvider); - // Test deployed FxChildTunnel address on mumbai const fxChildTunnelAddress = "0x31D3202d8744B16A120117A053459DDFAE93c855"; const fxChildTunnelJSON = "artifacts/contracts/bridges/test/FxChildTunnel.sol/FxChildTunnel.json"; From c802cb9a8de7bc85a4585438ad460f53432555a4 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 10:17:33 +0100 Subject: [PATCH 04/14] chore: linter fix --- .../test/fx_goerli_mumbai_hello_world.js | 2 ++ .../test/fx_mumbai_goerli_token_transfer.js | 33 +++++-------------- .../wormhole/test/deploy_00_mock_timelock.js | 1 - .../deploy_03_womholel2receiverl1sender.js | 1 - .../l1_l2_l1/deploy_04_womholel1receiver.js | 1 - .../l1_l2_l1/deploy_05_womholel1sender.js | 1 - .../proposal_01_goerli_sync_mainnet.js | 2 -- scripts/proposals/proposal_04_CM_guard.js | 2 -- .../proposals/proposal_04_CM_guard_goerli.js | 2 -- scripts/proposals/proposal_05_CM_guard.js | 1 - 10 files changed, 10 insertions(+), 36 deletions(-) diff --git a/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js b/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js index 568a47b..d193d90 100644 --- a/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js +++ b/scripts/deployment/bridges/polygon/test/fx_goerli_mumbai_hello_world.js @@ -34,6 +34,8 @@ async function main() { const fxChildTunnelABI = parsedFile["abi"]; const fxChildTunnel = new ethers.Contract(fxChildTunnelAddress, fxChildTunnelABI, mumbaiProvider); const verifyFxChildAddress = await fxChildTunnel.fxChild(); + + const fxChildAddress = "0xCf73231F28B7331BBe3124B907840A94851f9f11"; if (fxChildAddress == verifyFxChildAddress) { console.log("Successfully connected to the test fxChildTunnel contract"); } diff --git a/scripts/deployment/bridges/polygon/test/fx_mumbai_goerli_token_transfer.js b/scripts/deployment/bridges/polygon/test/fx_mumbai_goerli_token_transfer.js index a63cf42..03af9af 100644 --- a/scripts/deployment/bridges/polygon/test/fx_mumbai_goerli_token_transfer.js +++ b/scripts/deployment/bridges/polygon/test/fx_mumbai_goerli_token_transfer.js @@ -18,38 +18,15 @@ async function main() { }); const fs = require("fs"); - // FxRoot address on goerli - const fxRootAddress = "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA"; - const fxRootJSON = "artifacts/lib/fx-portal/contracts/FxRoot.sol/FxRoot.json"; - let contractFromJSON = fs.readFileSync(fxRootJSON, "utf8"); - let parsedFile = JSON.parse(contractFromJSON); - const fxRootABI = parsedFile["abi"]; - const fxRoot = new ethers.Contract(fxRootAddress, fxRootABI, goerliProvider); - - // FxChild address on mumbai - const fxChildAddress = "0xCf73231F28B7331BBe3124B907840A94851f9f11"; - const fxChildJSON = "artifacts/lib/fx-portal/contracts/FxChild.sol/FxChild.json"; - contractFromJSON = fs.readFileSync(fxChildJSON, "utf8"); - parsedFile = JSON.parse(contractFromJSON); - const fxChildABI = parsedFile["abi"]; - const fxChild = new ethers.Contract(fxChildAddress, fxChildABI, mumbaiProvider); // ChildMockERC20 address on mumbai const mockChildERC20Address = "0xeB49bE5DF00F74bd240DE4535DDe6Bc89CEfb994"; const mockChildERC20JSON = "artifacts/contracts/bridges/test/ChildMockERC20.sol/ChildMockERC20.json"; - contractFromJSON = fs.readFileSync(mockChildERC20JSON, "utf8"); - parsedFile = JSON.parse(contractFromJSON); + let contractFromJSON = fs.readFileSync(mockChildERC20JSON, "utf8"); + let parsedFile = JSON.parse(contractFromJSON); const mockChildERC20ABI = parsedFile["abi"]; const mockChildERC20 = new ethers.Contract(mockChildERC20Address, mockChildERC20ABI, mumbaiProvider); - // BridgedERC20 address on goerli - const bridgedERC20Address = "0x88e4ad16Bd4953Bbe74589942b368969037a7d81"; - const bridgedERC20JSON = "artifacts/contracts/bridges/BridgedERC20.sol/BridgedERC20.json"; - contractFromJSON = fs.readFileSync(bridgedERC20JSON, "utf8"); - parsedFile = JSON.parse(contractFromJSON); - const bridgedERC20ABI = parsedFile["abi"]; - const bridgedERC20 = new ethers.Contract(bridgedERC20Address, bridgedERC20ABI, goerliProvider); - // Test deployed FxERC20ChildTunnel address on mumbai const fxERC20ChildTunnelAddress = "0x1d333b46dB6e8FFd271b6C2D2B254868BD9A2dbd"; const fxERC20ChildTunnelJSON = "artifacts/contracts/bridges/FxERC20ChildTunnel.sol/FxERC20ChildTunnel.json"; @@ -58,6 +35,9 @@ async function main() { const fxERC20ChildTunnelABI = parsedFile["abi"]; const fxERC20ChildTunnel = new ethers.Contract(fxERC20ChildTunnelAddress, fxERC20ChildTunnelABI, mumbaiProvider); const verifyFxChildAddress = await fxERC20ChildTunnel.fxChild(); + + // FxChild address on mumbai + const fxChildAddress = "0xCf73231F28B7331BBe3124B907840A94851f9f11"; if (fxChildAddress == verifyFxChildAddress) { console.log("Successfully connected to the test fxERC20ChildTunnel contract"); } @@ -70,6 +50,9 @@ async function main() { const fxERC20RootTunnelABI = parsedFile["abi"]; const fxERC20RootTunnel = new ethers.Contract(fxERC20RootTunnelAddress, fxERC20RootTunnelABI, goerliProvider); const verifyFxRootAddress = await fxERC20RootTunnel.fxRoot(); + + // FxRoot address on goerli + const fxRootAddress = "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA"; if (fxRootAddress == verifyFxRootAddress) { console.log("Successfully connected to the test fxERC20RootTunnel contract"); } diff --git a/scripts/deployment/bridges/wormhole/test/deploy_00_mock_timelock.js b/scripts/deployment/bridges/wormhole/test/deploy_00_mock_timelock.js index 2594c6f..c7fb757 100644 --- a/scripts/deployment/bridges/wormhole/test/deploy_00_mock_timelock.js +++ b/scripts/deployment/bridges/wormhole/test/deploy_00_mock_timelock.js @@ -11,7 +11,6 @@ async function main() { const useLedger = parsedData.useLedger; const derivationPath = parsedData.derivationPath; const providerName = "sepolia"; - const gasPriceInGwei = parsedData.gasPriceInGwei; let EOA; const provider = await ethers.providers.getDefaultProvider(providerName); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js index 81460f8..c595da1 100644 --- a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js @@ -11,7 +11,6 @@ async function main() { const useLedger = parsedData.useLedger; const derivationPath = parsedData.derivationPath; const providerName = parsedData.providerName; - const gasPriceInGwei = parsedData.gasPriceInGwei; let EOA; const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js index 523d560..bc73235 100644 --- a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js @@ -11,7 +11,6 @@ async function main() { const useLedger = parsedData.useLedger; const derivationPath = parsedData.derivationPath; const providerName = "sepolia"; - const gasPriceInGwei = parsedData.gasPriceInGwei; let EOA; const provider = await ethers.providers.getDefaultProvider(providerName); diff --git a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js index 617f615..7653cc5 100644 --- a/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js +++ b/scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js @@ -11,7 +11,6 @@ async function main() { const useLedger = parsedData.useLedger; const derivationPath = parsedData.derivationPath; const providerName = parsedData.providerName; - const gasPriceInGwei = parsedData.gasPriceInGwei; let EOA; const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL); diff --git a/scripts/proposals/proposal_01_goerli_sync_mainnet.js b/scripts/proposals/proposal_01_goerli_sync_mainnet.js index 942c344..d1c0db3 100644 --- a/scripts/proposals/proposal_01_goerli_sync_mainnet.js +++ b/scripts/proposals/proposal_01_goerli_sync_mainnet.js @@ -7,9 +7,7 @@ async function main() { const globalsFile = "globals.json"; const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); let parsedData = JSON.parse(dataFromJSON); - const providerName = parsedData.providerName; - const provider = await ethers.providers.getDefaultProvider(providerName); const signers = await ethers.getSigners(); // EOA address diff --git a/scripts/proposals/proposal_04_CM_guard.js b/scripts/proposals/proposal_04_CM_guard.js index b96bae2..1dc3897 100644 --- a/scripts/proposals/proposal_04_CM_guard.js +++ b/scripts/proposals/proposal_04_CM_guard.js @@ -8,7 +8,6 @@ async function main() { const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); let parsedData = JSON.parse(dataFromJSON); - const timelockAddress = parsedData.timelockAddress; const treasuryAddress = parsedData.treasuryAddress; const depositoryAddress = parsedData.depositoryAddress; const serviceRegistryTokenUtilityAddress = "0x3Fb926116D454b95c669B6Bf2E7c3bad8d19affA"; @@ -20,7 +19,6 @@ async function main() { const homeMediatorAddress = "0x15bd56669F57192a97dF41A2aa8f4403e9491776"; const fxRootAddress = parsedData.fxRootAddress; const fxGovernorTunnelAddress = "0x9338b5153AE39BB89f50468E608eD9d764B755fD"; - const CMAddress = parsedData.CM; diff --git a/scripts/proposals/proposal_04_CM_guard_goerli.js b/scripts/proposals/proposal_04_CM_guard_goerli.js index 1a8d096..8af273e 100644 --- a/scripts/proposals/proposal_04_CM_guard_goerli.js +++ b/scripts/proposals/proposal_04_CM_guard_goerli.js @@ -8,7 +8,6 @@ async function main() { const dataFromJSON = fs.readFileSync(globalsFile, "utf8"); let parsedData = JSON.parse(dataFromJSON); - const timelockAddress = parsedData.timelockAddress; const treasuryAddress = parsedData.treasuryAddress; const depositoryAddress = "0x5FDc466f4A7547c876eF40CD30fFA2A89F1EcDE7"; const serviceRegistryTokenUtilityAddress = "0x6d9b08701Af43D68D991c074A27E4d90Af7f2276"; @@ -21,7 +20,6 @@ async function main() { const homeMediatorAddress = "0x670Ac235EE13C0B2a5065282bBB0c61cfB354592"; const fxRootAddress = "0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA"; const fxGovernorTunnelAddress = "0x17806E2a12d5E0F48C9803cd397DB3F044DA3b77"; - const CMAddress = parsedData.CM; diff --git a/scripts/proposals/proposal_05_CM_guard.js b/scripts/proposals/proposal_05_CM_guard.js index a816bee..5e65435 100644 --- a/scripts/proposals/proposal_05_CM_guard.js +++ b/scripts/proposals/proposal_05_CM_guard.js @@ -13,7 +13,6 @@ async function main() { const multisig = await ethers.getContractAt("GnosisSafe", parsedData.CM); const nonce = await multisig.nonce(); - const timelockAddress = parsedData.timelockAddress; const guardCMAddress = parsedData.guardCMAddress; // Construct the payload for the multisig to swap the guard by the Timelock From 9e6c8eec5e06dc619b65a4cf07c9b6358d143ff8 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 10:27:15 +0100 Subject: [PATCH 05/14] test: vote weighting initial testing --- test/Deployment.js | 12 ++-- test/GovernanceOLAS.js | 42 ++++++------- test/GovernanceTwoOLAS.js | 42 ++++++------- test/buOLAS.js | 30 +++++----- test/veOLAS.js | 120 +++++++++++++++++++------------------- test/wveOLAS.js | 94 ++++++++++++++--------------- 6 files changed, 170 insertions(+), 170 deletions(-) diff --git a/test/Deployment.js b/test/Deployment.js index 9543f4b..eab220b 100644 --- a/test/Deployment.js +++ b/test/Deployment.js @@ -23,10 +23,10 @@ describe("Deployment", function () { const fs = require("fs"); let gnosisSafeL2; let gnosisSafeProxyFactory; - const _1kOLABalance = "1000" + "0".repeat(18); - const _2kOLABalance = "2000" + "0".repeat(18); - const _3kOLABalance = "3000" + "0".repeat(18); - const _4kOLABalance = "4000" + "0".repeat(18); + const _1kOLASBalance = "1000" + "0".repeat(18); + const _2kOLASBalance = "2000" + "0".repeat(18); + const _3kOLASBalance = "3000" + "0".repeat(18); + const _4kOLASBalance = "4000" + "0".repeat(18); const oneYear = 365 * 86400; let veOLASSigners; let buOLASSigners; @@ -78,12 +78,12 @@ describe("Deployment", function () { let claimableBalancesJSON = { "veOLAS": { "addresses": [veOLASSigners[0].address, veOLASSigners[1].address, veOLASSigners[2].address], - "amounts": [_1kOLABalance, _2kOLABalance, _3kOLABalance], + "amounts": [_1kOLASBalance, _2kOLASBalance, _3kOLASBalance], "lockTimes": [oneYear, 2 * oneYear, 3 * oneYear] }, "buOLAS": { "addresses": [buOLASSigners[0].address, buOLASSigners[1].address, buOLASSigners[2].address, buOLASSigners[3].address], - "amounts": [_1kOLABalance, _2kOLABalance, _3kOLABalance, _4kOLABalance], + "amounts": [_1kOLASBalance, _2kOLASBalance, _3kOLASBalance, _4kOLASBalance], "numSteps": [1, 2, 3, 4] } }; diff --git a/test/GovernanceOLAS.js b/test/GovernanceOLAS.js index 59a09ba..0e73953 100644 --- a/test/GovernanceOLAS.js +++ b/test/GovernanceOLAS.js @@ -10,10 +10,10 @@ describe("Governance OLAS", function () { let ve; let signers; const oneWeek = 7 * 86400; - const oneOLABalance = ethers.utils.parseEther("1"); - const twoOLABalance = ethers.utils.parseEther("2"); - const fiveOLABalance = ethers.utils.parseEther("5"); - const tenOLABalance = ethers.utils.parseEther("10"); + const oneOLASBalance = ethers.utils.parseEther("1"); + const twoOLASBalance = ethers.utils.parseEther("2"); + const fiveOLASBalance = ethers.utils.parseEther("5"); + const tenOLASBalance = ethers.utils.parseEther("10"); const AddressZero = "0x" + "0".repeat(40); const bytes32Zero = "0x" + "0".repeat(64); const safeThreshold = 7; @@ -21,7 +21,7 @@ describe("Governance OLAS", function () { const minDelay = 1; // blocks const initialVotingDelay = 0; // blocks const initialVotingPeriod = 1; // blocks - const initialProposalThreshold = fiveOLABalance; // required voting power + const initialProposalThreshold = fiveOLASBalance; // required voting power const quorum = 1; // quorum factor const proposalDescription = "Proposal 0"; beforeEach(async function () { @@ -44,7 +44,7 @@ describe("Governance OLAS", function () { signers = await ethers.getSigners(); // Mint 10 OLAS worth of OLAS tokens by default - await token.mint(signers[0].address, tenOLABalance); + await token.mint(signers[0].address, tenOLASBalance); const balance = await token.balanceOf(signers[0].address); expect(ethers.utils.formatEther(balance) == 10).to.be.true; }); @@ -106,14 +106,14 @@ describe("Governance OLAS", function () { it("Changes the ownership of a governance contract and a timelock", async function () { const deployer = signers[0]; // Approve signers[0] for 10 OLAS by voting ve - await token.approve(ve.address, tenOLABalance); + await token.approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration in Voting Escrow. // This will result in voting power being almost exactly as OLAS amount locked: // voting power = amount * t_left_before_unlock / t_max const lockDuration = 4 * 365 * 86400; // Lock 10 OLAS, which is enough to cover the 5 OLAS of initial proposal threshold voting power - await ve.createLock(tenOLABalance, lockDuration); + await ve.createLock(tenOLASBalance, lockDuration); // Deploy first timelock const executors = [deployer.address]; @@ -198,7 +198,7 @@ describe("Governance OLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLA by voting ve - await token.connect(deployer).approve(ve.address, tenOLABalance); + await token.connect(deployer).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLA amount locked: @@ -207,9 +207,9 @@ describe("Governance OLAS", function () { const lockDuration = fourYears; // Lock 5 OLA, which is lower than the initial proposal threshold by a bit - await ve.connect(deployer).createLock(fiveOLABalance, lockDuration); + await ve.connect(deployer).createLock(fiveOLASBalance, lockDuration); // Add a bit more - await ve.connect(deployer).increaseAmount(oneOLABalance); + await ve.connect(deployer).increaseAmount(oneOLASBalance); // Deploy Timelock const executors = []; @@ -267,23 +267,23 @@ describe("Governance OLAS", function () { // Transfer initial balances to all the gelegators: 1 OLAS to each for (let i = 1; i <= numDelegators; i++) { - await token.transfer(signers[i].address, oneOLABalance); + await token.transfer(signers[i].address, oneOLASBalance); const balance = await token.balanceOf(signers[i].address); expect(ethers.utils.formatEther(balance) == 1).to.be.true; } // Approve signers[1]-signers[10] for 1 OLAS by voting ve for (let i = 1; i <= numDelegators; i++) { - await token.connect(signers[i]).approve(ve.address, oneOLABalance); + await token.connect(signers[i]).approve(ve.address, oneOLASBalance); } // Define 1 week for the lock duration const lockDuration = oneWeek; // Deposit tokens as a voting power to a chosen delegatee - await ve.connect(signers[1]).createLock(oneOLABalance, lockDuration); + await ve.connect(signers[1]).createLock(oneOLASBalance, lockDuration); for (let i = 2; i <= numDelegators; i++) { - await ve.connect(signers[i]).depositFor(delegatee, oneOLABalance); + await ve.connect(signers[i]).depositFor(delegatee, oneOLASBalance); } // Given 1 OLAS worth of voting power from every address, the cumulative voting power must be 10 @@ -301,7 +301,7 @@ describe("Governance OLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLAS by voting ve - await token.connect(signers[0]).approve(ve.address, tenOLABalance); + await token.connect(signers[0]).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLAS amount locked: @@ -310,7 +310,7 @@ describe("Governance OLAS", function () { const lockDuration = fourYears; // Lock 5 OLAS, which is lower than the initial proposal threshold by a bit - await ve.connect(signers[0]).createLock(fiveOLABalance, lockDuration); + await ve.connect(signers[0]).createLock(fiveOLASBalance, lockDuration); // Deploy simple version of a timelock const executors = []; @@ -333,7 +333,7 @@ describe("Governance OLAS", function () { ).to.be.revertedWith("Governor: proposer votes below proposal threshold"); // Adding voting power, and the proposal must go through, 4 + 2 of OLAS in voting power is almost 6 > 5 required - await ve.connect(signers[0]).increaseAmount(twoOLABalance); + await ve.connect(signers[0]).increaseAmount(twoOLASBalance); await governor.connect(signers[0])["propose(address[],uint256[],bytes[],string)"]([AddressZero], [0], ["0x"], proposalDescription); }); @@ -344,7 +344,7 @@ describe("Governance OLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLA by voting ve - await token.connect(deployer).approve(ve.address, tenOLABalance); + await token.connect(deployer).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLA amount locked: @@ -353,9 +353,9 @@ describe("Governance OLAS", function () { const lockDuration = fourYears; // Lock 5 OLA, which is lower than the initial proposal threshold by a bit - await ve.connect(deployer).createLock(fiveOLABalance, lockDuration); + await ve.connect(deployer).createLock(fiveOLASBalance, lockDuration); // Add a bit more - await ve.connect(deployer).increaseAmount(oneOLABalance); + await ve.connect(deployer).increaseAmount(oneOLASBalance); // Deploy Timelock const proposers = [deployer.address]; diff --git a/test/GovernanceTwoOLAS.js b/test/GovernanceTwoOLAS.js index b801182..9013c89 100644 --- a/test/GovernanceTwoOLAS.js +++ b/test/GovernanceTwoOLAS.js @@ -11,10 +11,10 @@ describe("Governance OLAS on wveOLAS", function () { let wve; let signers; const oneWeek = 7 * 86400; - const oneOLABalance = ethers.utils.parseEther("1"); - const twoOLABalance = ethers.utils.parseEther("2"); - const fiveOLABalance = ethers.utils.parseEther("5"); - const tenOLABalance = ethers.utils.parseEther("10"); + const oneOLASBalance = ethers.utils.parseEther("1"); + const twoOLASBalance = ethers.utils.parseEther("2"); + const fiveOLASBalance = ethers.utils.parseEther("5"); + const tenOLASBalance = ethers.utils.parseEther("10"); const AddressZero = "0x" + "0".repeat(40); const bytes32Zero = "0x" + "0".repeat(64); const safeThreshold = 7; @@ -22,7 +22,7 @@ describe("Governance OLAS on wveOLAS", function () { const minDelay = 1; // blocks const initialVotingDelay = 0; // blocks const initialVotingPeriod = 1; // blocks - const initialProposalThreshold = fiveOLABalance; // required voting power + const initialProposalThreshold = fiveOLASBalance; // required voting power const quorum = 1; // quorum factor const proposalDescription = "Proposal 0"; beforeEach(async function () { @@ -49,7 +49,7 @@ describe("Governance OLAS on wveOLAS", function () { signers = await ethers.getSigners(); // Mint 10 OLAS worth of OLAS tokens by default - await token.mint(signers[0].address, tenOLABalance); + await token.mint(signers[0].address, tenOLASBalance); const balance = await token.balanceOf(signers[0].address); expect(ethers.utils.formatEther(balance) == 10).to.be.true; }); @@ -111,14 +111,14 @@ describe("Governance OLAS on wveOLAS", function () { it("Changes the ownership of a governance contract and a timelock", async function () { const deployer = signers[0]; // Approve signers[0] for 10 OLAS by voting ve - await token.approve(ve.address, tenOLABalance); + await token.approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration in Voting Escrow. // This will result in voting power being almost exactly as OLAS amount locked: // voting power = amount * t_left_before_unlock / t_max const lockDuration = 4 * 365 * 86400; // Lock 10 OLAS, which is enough to cover the 5 OLAS of initial proposal threshold voting power - await ve.createLock(tenOLABalance, lockDuration); + await ve.createLock(tenOLASBalance, lockDuration); // Deploy first timelock const executors = [deployer.address]; @@ -203,7 +203,7 @@ describe("Governance OLAS on wveOLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLA by voting ve - await token.connect(deployer).approve(ve.address, tenOLABalance); + await token.connect(deployer).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLA amount locked: @@ -212,9 +212,9 @@ describe("Governance OLAS on wveOLAS", function () { const lockDuration = fourYears; // Lock 5 OLA, which is lower than the initial proposal threshold by a bit - await ve.connect(deployer).createLock(fiveOLABalance, lockDuration); + await ve.connect(deployer).createLock(fiveOLASBalance, lockDuration); // Add a bit more - await ve.connect(deployer).increaseAmount(oneOLABalance); + await ve.connect(deployer).increaseAmount(oneOLASBalance); // Deploy Timelock const executors = []; @@ -272,23 +272,23 @@ describe("Governance OLAS on wveOLAS", function () { // Transfer initial balances to all the gelegators: 1 OLAS to each for (let i = 1; i <= numDelegators; i++) { - await token.transfer(signers[i].address, oneOLABalance); + await token.transfer(signers[i].address, oneOLASBalance); const balance = await token.balanceOf(signers[i].address); expect(ethers.utils.formatEther(balance) == 1).to.be.true; } // Approve signers[1]-signers[10] for 1 OLAS by voting ve for (let i = 1; i <= numDelegators; i++) { - await token.connect(signers[i]).approve(ve.address, oneOLABalance); + await token.connect(signers[i]).approve(ve.address, oneOLASBalance); } // Define 1 week for the lock duration const lockDuration = oneWeek; // Deposit tokens as a voting power to a chosen delegatee - await ve.connect(signers[1]).createLock(oneOLABalance, lockDuration); + await ve.connect(signers[1]).createLock(oneOLASBalance, lockDuration); for (let i = 2; i <= numDelegators; i++) { - await ve.connect(signers[i]).depositFor(delegatee, oneOLABalance); + await ve.connect(signers[i]).depositFor(delegatee, oneOLASBalance); } // Given 1 OLAS worth of voting power from every address, the cumulative voting power must be 10 @@ -306,7 +306,7 @@ describe("Governance OLAS on wveOLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLAS by voting ve - await token.connect(signers[0]).approve(ve.address, tenOLABalance); + await token.connect(signers[0]).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLAS amount locked: @@ -315,7 +315,7 @@ describe("Governance OLAS on wveOLAS", function () { const lockDuration = fourYears; // Lock 5 OLAS, which is lower than the initial proposal threshold by a bit - await ve.connect(signers[0]).createLock(fiveOLABalance, lockDuration); + await ve.connect(signers[0]).createLock(fiveOLASBalance, lockDuration); // Deploy simple version of a timelock const executors = []; @@ -338,7 +338,7 @@ describe("Governance OLAS on wveOLAS", function () { ).to.be.revertedWith("Governor: proposer votes below proposal threshold"); // Adding voting power, and the proposal must go through, 4 + 2 of OLAS in voting power is almost 6 > 5 required - await ve.connect(signers[0]).increaseAmount(twoOLABalance); + await ve.connect(signers[0]).increaseAmount(twoOLASBalance); await governor.connect(signers[0])["propose(address[],uint256[],bytes[],string)"]([AddressZero], [0], ["0x"], proposalDescription); }); @@ -349,7 +349,7 @@ describe("Governance OLAS on wveOLAS", function () { expect(ethers.utils.formatEther(balance) == 10).to.be.true; // Approve signers[0] for 10 OLA by voting ve - await token.connect(deployer).approve(ve.address, tenOLABalance); + await token.connect(deployer).approve(ve.address, tenOLASBalance); // Define 4 years for the lock duration. // This will result in voting power being almost exactly as OLA amount locked: @@ -358,9 +358,9 @@ describe("Governance OLAS on wveOLAS", function () { const lockDuration = fourYears; // Lock 5 OLA, which is lower than the initial proposal threshold by a bit - await ve.connect(deployer).createLock(fiveOLABalance, lockDuration); + await ve.connect(deployer).createLock(fiveOLASBalance, lockDuration); // Add a bit more - await ve.connect(deployer).increaseAmount(oneOLABalance); + await ve.connect(deployer).increaseAmount(oneOLASBalance); // Deploy Timelock const proposers = [deployer.address]; diff --git a/test/buOLAS.js b/test/buOLAS.js index 13cd271..91478b3 100644 --- a/test/buOLAS.js +++ b/test/buOLAS.js @@ -10,7 +10,7 @@ describe("buOLAS", function () { let signers; const initialMint = "1000000000000000000000000"; // 1000000 const oneYear = 365 * 86400; - const quarterOLABalance = ethers.utils.parseEther("0.25"); + const quarterOLASBalance = ethers.utils.parseEther("0.25"); const oneOLASBalance = ethers.utils.parseEther("1"); const twoOLASBalance = ethers.utils.parseEther("2"); const AddressZero = "0x" + "0".repeat(40); @@ -128,7 +128,7 @@ describe("buOLAS", function () { ethers.provider.send("evm_mine"); // Now the releasable amount must be equal to 1/4 of the total amount amount = await bu.releasableAmount(account.address); - expect(amount).to.equal(quarterOLABalance); + expect(amount).to.equal(quarterOLASBalance); // Move five years in time ethers.provider.send("evm_increaseTime", [5 * oneYear + 100]); @@ -167,7 +167,7 @@ describe("buOLAS", function () { // Withdraw must be equal to 1/4 of the total amount expect(await olas.balanceOf(account.address)).to.equal(0); await bu.connect(account).withdraw(); - expect(await olas.balanceOf(account.address)).to.equal(quarterOLABalance); + expect(await olas.balanceOf(account.address)).to.equal(quarterOLASBalance); // Try to withdraw more now await expect( @@ -198,10 +198,10 @@ describe("buOLAS", function () { ethers.provider.send("evm_increaseTime", [oneYear + 100]); ethers.provider.send("evm_mine"); // Withdraw must be equal to rounded 1/3 of the total amount - const thirdOLABalance = new ethers.BigNumber.from(oneOLASBalance).div(numSteps); + const thirdOLASBalance = new ethers.BigNumber.from(oneOLASBalance).div(numSteps); await bu.connect(account).withdraw(); - const recoveredFullBalance = thirdOLABalance.mul(numSteps); - expect(await olas.balanceOf(account.address)).to.equal(thirdOLABalance); + const recoveredFullBalance = thirdOLASBalance.mul(numSteps); + expect(await olas.balanceOf(account.address)).to.equal(thirdOLASBalance); // This proves that we can potentially lose only 1e(-18) tokens if we call revoke on non divisible remainder expect(recoveredFullBalance.add(1)).to.equal(oneOLASBalance); @@ -240,7 +240,7 @@ describe("buOLAS", function () { await bu.connect(owner).revoke([account.address]); // The buOLAS balanceOf must be equal to the releasable amount after the revoke balance = await bu.balanceOf(account.address); - expect(balance).to.equal(quarterOLABalance); + expect(balance).to.equal(quarterOLASBalance); // Move time after the full lock duration ethers.provider.send("evm_increaseTime", [3 * oneYear + 100]); @@ -248,7 +248,7 @@ describe("buOLAS", function () { // The releasable amount must still be the 1/4 amount, since the rest was revoked let amount = await bu.releasableAmount(account.address); - expect(amount).to.equal(quarterOLABalance); + expect(amount).to.equal(quarterOLASBalance); // Withdraw must be equal to 1/4 of the total amount since another 3/4 has been revoked expect(await olas.balanceOf(account.address)).to.equal(0); @@ -257,7 +257,7 @@ describe("buOLAS", function () { expect(supply).to.equal(oneOLASBalance); // Withdraw what we can for the account await bu.connect(account).withdraw(); - expect(await olas.balanceOf(account.address)).to.equal(quarterOLABalance); + expect(await olas.balanceOf(account.address)).to.equal(quarterOLASBalance); // Now the balance is zero, since the rest of 3/4 tokens were burned supply = await bu.totalSupply(); expect(supply).to.equal(0); @@ -290,21 +290,21 @@ describe("buOLAS", function () { // Revoke at this point of time await bu.connect(owner).revoke([account.address]); // The buOLAS balanceOf must be equal to the releasable amount after the revoke, which is 1/3 - const thirdOLABalance = new ethers.BigNumber.from(oneOLASBalance).div(numSteps); + const thirdOLASBalance = new ethers.BigNumber.from(oneOLASBalance).div(numSteps); const balance = await bu.balanceOf(account.address); - expect(balance).to.equal(thirdOLABalance); + expect(balance).to.equal(thirdOLASBalance); // The releasable amount must be the 1/3 amount, since the rest 1/3 was revoked let amount = await bu.releasableAmount(account.address); - expect(amount).to.equal(thirdOLABalance); + expect(amount).to.equal(thirdOLASBalance); - const twoThirdsOLABalance = thirdOLABalance.mul(2); + const twoThirdsOLASBalance = thirdOLASBalance.mul(2); // Before the withdraw the total supply is equal to 2/3 of the full balance, since 1/3 was already withdrawn let supply = await bu.totalSupply(); - expect(supply).to.equal(twoThirdsOLABalance.add(1)); + expect(supply).to.equal(twoThirdsOLASBalance.add(1)); // Withdraw what we can for the account, after which the balance is 2/3 of the initial balance await bu.connect(account).withdraw(); - expect(await olas.balanceOf(account.address)).to.equal(twoThirdsOLABalance); + expect(await olas.balanceOf(account.address)).to.equal(twoThirdsOLASBalance); // Now the balance is zero, since the rest of 1/3 tokens were burned supply = await bu.totalSupply(); expect(supply).to.equal(0); diff --git a/test/veOLAS.js b/test/veOLAS.js index 8f0483a..444ee7d 100644 --- a/test/veOLAS.js +++ b/test/veOLAS.js @@ -9,9 +9,9 @@ describe("Voting Escrow OLAS", function () { let signers; const initialMint = "1000000000000000000000000"; // 1000000 const oneWeek = 7 * 86400; - const oneOLABalance = ethers.utils.parseEther("1"); - const twoOLABalance = ethers.utils.parseEther("2"); - const tenOLABalance = ethers.utils.parseEther("10"); + const oneOLASBalance = ethers.utils.parseEther("1"); + const twoOLASBalance = ethers.utils.parseEther("2"); + const tenOLASBalance = ethers.utils.parseEther("10"); const AddressZero = "0x" + "0".repeat(40); const overflowNum96 = "8" + "0".repeat(28); @@ -57,14 +57,14 @@ describe("Voting Escrow OLAS", function () { }); it("Should fail when creating a lock with zero value or wrong duration", async function () { - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); await expect( ve.createLock(0, 0) ).to.be.revertedWithCustomError(ve, "ZeroValue"); await expect( - ve.createLock(oneOLABalance, 0) + ve.createLock(oneOLASBalance, 0) ).to.be.revertedWithCustomError(ve, "UnlockTimeIncorrect"); await expect( @@ -75,19 +75,19 @@ describe("Voting Escrow OLAS", function () { it("Create lock", async function () { // Transfer 10 OLAS to signers[1] const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve signers[0] and signers[1] for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now // Balance should be zero before the lock expect(await ve.getVotes(owner.address)).to.equal(0); - await ve.createLock(oneOLABalance, lockDuration); - await ve.connect(owner).createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); + await ve.connect(owner).createLock(oneOLASBalance, lockDuration); // Lock end is rounded by 1 week, as implemented by design const lockEnd = await ve.lockedEnd(owner.address); @@ -97,7 +97,7 @@ describe("Voting Escrow OLAS", function () { // Get the account of the last user point const pv = await ve.getLastUserPoint(owner.address); - expect(pv.balance).to.equal(oneOLABalance); + expect(pv.balance).to.equal(oneOLASBalance); // Get the number of user points for owner and compare the balance of the last point const numAccountPoints = await ve.getNumUserPoints(owner.address); @@ -118,7 +118,7 @@ describe("Voting Escrow OLAS", function () { const account = signers[1]; // Approve owner for 1 OLAS by veOLAS - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now @@ -127,11 +127,11 @@ describe("Voting Escrow OLAS", function () { expect(await ve.getVotes(account.address)).to.equal(0); // Try to create lock for the zero address await expect( - ve.connect(owner).createLockFor(AddressZero, oneOLABalance, lockDuration) + ve.connect(owner).createLockFor(AddressZero, oneOLASBalance, lockDuration) ).to.be.revertedWithCustomError(ve, "ZeroAddress"); // Lock for the account from the funds of the owner (approved for veOLAS) - await ve.connect(owner).createLockFor(account.address, oneOLABalance, lockDuration); + await ve.connect(owner).createLockFor(account.address, oneOLASBalance, lockDuration); // Lock end is rounded by 1 week, as implemented by design const lockEnd = await ve.lockedEnd(account.address); @@ -141,7 +141,7 @@ describe("Voting Escrow OLAS", function () { // Get the account of the last user point const pv = await ve.getLastUserPoint(account.address); - expect(pv.balance).to.equal(oneOLABalance); + expect(pv.balance).to.equal(oneOLASBalance); // Get the number of user points for owner and compare the balance of the last point const numAccountPoints = await ve.getNumUserPoints(account.address); @@ -154,27 +154,27 @@ describe("Voting Escrow OLAS", function () { const deployer = signers[0]; // Transfer 10 OLAS to signers[1] const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve deployer for 2 OLAS by voting escrow - await olas.approve(ve.address, twoOLABalance); + await olas.approve(ve.address, twoOLASBalance); // Approve owner for 1 OLAS by voting escrow - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now // Try to deposit 1 OLAS for deployer without initially locked balance await expect( - ve.depositFor(deployer.address, oneOLABalance) + ve.depositFor(deployer.address, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NoValueLocked"); // Create lock for the deployer - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Try to lock the remainder of 1 OLAS for deployer from the account that did not approve for veOLAS await expect( - ve.connect(signers[2]).depositFor(deployer.address, oneOLABalance) + ve.connect(signers[2]).depositFor(deployer.address, oneOLASBalance) ).to.be.reverted; // Try to deposit zero value for deployer @@ -188,53 +188,53 @@ describe("Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(ve, "Overflow"); // Deposit for the deployer from the - await ve.connect(owner).depositFor(deployer.address, oneOLABalance); + await ve.connect(owner).depositFor(deployer.address, oneOLASBalance); // Check the balance of deployer (must be twice of his initial one) const balanceDeployer = await ve.balanceOf(deployer.address); - expect(balanceDeployer).to.equal(twoOLABalance); + expect(balanceDeployer).to.equal(twoOLASBalance); // Try to deposit 1 OLAS for deployer after its lock time hase expired ethers.provider.send("evm_increaseTime", [oneWeek + 1000]); ethers.provider.send("evm_mine"); await expect( - ve.depositFor(deployer.address, oneOLABalance) + ve.depositFor(deployer.address, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "LockExpired"); }); it("Should fail when creating a lock for more than 4 years", async function () { const fourYears = 4 * 365 * oneWeek / 7; - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); const lockDuration = fourYears + oneWeek; // 4 years and 1 week await expect( - ve.createLock(oneOLABalance, lockDuration) + ve.createLock(oneOLASBalance, lockDuration) ).to.be.revertedWithCustomError(ve, "MaxUnlockTimeReached"); }); it("Should fail when creating a lock with already locked value", async function () { - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); const lockDuration = oneWeek; - ve.createLock(oneOLABalance, lockDuration); + ve.createLock(oneOLASBalance, lockDuration); await expect( - ve.createLock(oneOLABalance, lockDuration) + ve.createLock(oneOLASBalance, lockDuration) ).to.be.revertedWithCustomError(ve, "LockedValueNotZero"); }); it("Increase amount of lock", async function () { - await olas.approve(ve.address, tenOLABalance); + await olas.approve(ve.address, tenOLASBalance); const lockDuration = oneWeek; // Should fail if requires are not satisfied // No previous lock await expect( - ve.increaseAmount(oneOLABalance) + ve.increaseAmount(oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NoValueLocked"); // Now lock 1 OLAS - ve.createLock(oneOLABalance, lockDuration); + ve.createLock(oneOLASBalance, lockDuration); // Increase by more than a zero await expect( ve.increaseAmount(0) @@ -246,7 +246,7 @@ describe("Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(ve, "Overflow"); // Add 1 OLAS more - await ve.increaseAmount(oneOLABalance); + await ve.increaseAmount(oneOLASBalance); // Time forward to the lock expiration ethers.provider.send("evm_increaseTime", [oneWeek]); @@ -254,12 +254,12 @@ describe("Voting Escrow OLAS", function () { // Not possible to add to the expired lock await expect( - ve.increaseAmount(oneOLABalance) + ve.increaseAmount(oneOLASBalance) ).to.be.revertedWithCustomError(ve, "LockExpired"); }); it("Increase amount of unlock time", async function () { - await olas.approve(ve.address, tenOLABalance); + await olas.approve(ve.address, tenOLASBalance); const lockDuration = oneWeek; // Should fail if requires are not satisfied @@ -269,7 +269,7 @@ describe("Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(ve, "NoValueLocked"); // Lock 1 OLAS - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Try to decrease the unlock time await expect( ve.increaseUnlockTime(lockDuration - 1) @@ -297,12 +297,12 @@ describe("Voting Escrow OLAS", function () { it("Withdraw", async function () { // Transfer 2 OLAS to signers[1] and approve the voting escrow for 1 OLAS const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.transfer(owner.address, tenOLASBalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Lock 1 OLAS const lockDuration = 2 * oneWeek; - await ve.connect(owner).createLock(oneOLABalance, lockDuration); + await ve.connect(owner).createLock(oneOLASBalance, lockDuration); // Try withdraw early await expect(ve.connect(owner).withdraw()).to.be.revertedWithCustomError(ve, "LockNotExpired"); @@ -323,7 +323,7 @@ describe("Voting Escrow OLAS", function () { // Now withdraw must work await ve.connect(owner).withdraw(); - expect(await olas.balanceOf(owner.address)).to.equal(tenOLABalance); + expect(await olas.balanceOf(owner.address)).to.equal(tenOLASBalance); }); }); @@ -332,11 +332,11 @@ describe("Voting Escrow OLAS", function () { // Transfer 10 OLAS worth of OLAS to signers[1] const deployer = signers[0]; const account = signers[1]; - await olas.transfer(account.address, tenOLABalance); + await olas.transfer(account.address, tenOLASBalance); // Approve deployer and account for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); - await olas.connect(account).approve(ve.address, tenOLABalance); + await olas.approve(ve.address, oneOLASBalance); + await olas.connect(account).approve(ve.address, tenOLASBalance); // Initial total supply must be 0 expect(await ve.totalSupply()).to.equal(0); @@ -345,8 +345,8 @@ describe("Voting Escrow OLAS", function () { const lockDuration = oneWeek; // 1 week from now // Create locks for both addresses deployer and account - await ve.createLock(oneOLABalance, lockDuration); - await ve.connect(account).createLock(twoOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); + await ve.connect(account).createLock(twoOLASBalance, lockDuration); // Balance is time-based, it changes slightly every fraction of a time // Use both balances to check for the supply @@ -390,13 +390,13 @@ describe("Voting Escrow OLAS", function () { it("Checkpoint with points of inactivity", async function () { // Approve deployer and account for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); // Lock for four years const lockDuration = 4 * 365 * oneWeek / 7; // Create locks for both addresses deployer and account - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Move 10 weeks in time for (let i = 0; i < 10; ++i) { @@ -422,22 +422,22 @@ describe("Voting Escrow OLAS", function () { // Transfer 10 OLAS worth of OLAS to signers[1] const deployer = signers[0]; const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve signers[0] and signers[1] for 1 OLAS by voting escrow - await olas.approve(ve.address, tenOLABalance); - await olas.connect(owner).approve(ve.address, tenOLABalance); + await olas.approve(ve.address, tenOLASBalance); + await olas.connect(owner).approve(ve.address, tenOLASBalance); // Define 1 week for the lock duration let lockDuration = oneWeek; // Create and increase locks for both addresses signers[0] and signers[1] - await ve.createLock(twoOLABalance, lockDuration); - await ve.increaseAmount(oneOLABalance); + await ve.createLock(twoOLASBalance, lockDuration); + await ve.increaseAmount(oneOLASBalance); let blockNumber = await ethers.provider.getBlockNumber(); - await ve.connect(owner).createLock(twoOLABalance, lockDuration); - await ve.connect(owner).increaseAmount(oneOLABalance); - await ve.connect(owner).increaseAmount(oneOLABalance); + await ve.connect(owner).createLock(twoOLASBalance, lockDuration); + await ve.connect(owner).increaseAmount(oneOLASBalance); + await ve.connect(owner).increaseAmount(oneOLASBalance); // Get past votes of the owner (bug resolved in wveOLAS) const votesOwner = await ve.getPastVotes(owner.address, blockNumber); @@ -462,7 +462,7 @@ describe("Voting Escrow OLAS", function () { const deployer = signers[0].address; const user = signers[1].address; // Approve signers[0] for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); // Initial total supply must be 0 expect(await ve.totalSupply()).to.equal(0); @@ -471,20 +471,20 @@ describe("Voting Escrow OLAS", function () { const lockDuration = oneWeek; // 1 week from now // Create locks for both addresses signers[0] and signers[1] - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Try to call transfer-related functions for veOLAS await expect( - ve.approve(user, oneOLABalance) + ve.approve(user, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NonTransferable"); await expect( ve.allowance(deployer, user) ).to.be.revertedWithCustomError(ve, "NonTransferable"); await expect( - ve.transfer(user, oneOLABalance) + ve.transfer(user, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NonTransferable"); await expect( - ve.transferFrom(deployer, user, oneOLABalance) + ve.transferFrom(deployer, user, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NonTransferable"); // Try to call delegate-related functions for veOLAS diff --git a/test/wveOLAS.js b/test/wveOLAS.js index 342b3a3..f2bed7c 100644 --- a/test/wveOLAS.js +++ b/test/wveOLAS.js @@ -10,9 +10,9 @@ describe("Wrapped Voting Escrow OLAS", function () { let signers; const initialMint = "1000000000000000000000000"; // 1000000 const oneWeek = 7 * 86400; - const oneOLABalance = ethers.utils.parseEther("1"); - const twoOLABalance = ethers.utils.parseEther("2"); - const tenOLABalance = ethers.utils.parseEther("10"); + const oneOLASBalance = ethers.utils.parseEther("1"); + const twoOLASBalance = ethers.utils.parseEther("2"); + const tenOLASBalance = ethers.utils.parseEther("10"); const AddressZero = "0x" + "0".repeat(40); const overflowNum96 = "8" + "0".repeat(28); @@ -77,19 +77,19 @@ describe("Wrapped Voting Escrow OLAS", function () { it("Create lock", async function () { // Transfer 10 OLAS to signers[1] const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve signers[0] and signers[1] for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now // Balance should be zero before the lock expect(await wve.getVotes(owner.address)).to.equal(0); - await ve.createLock(oneOLABalance, lockDuration); - await ve.connect(owner).createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); + await ve.connect(owner).createLock(oneOLASBalance, lockDuration); // Lock end is rounded by 1 week, as implemented by design const lockEnd = await wve.lockedEnd(owner.address); @@ -99,7 +99,7 @@ describe("Wrapped Voting Escrow OLAS", function () { // Get the account of the last user point const pv = await wve.getLastUserPoint(owner.address); - expect(pv.balance).to.equal(oneOLABalance); + expect(pv.balance).to.equal(oneOLASBalance); // Get the number of user points for owner and compare the balance of the last point const numAccountPoints = await wve.getNumUserPoints(owner.address); @@ -120,7 +120,7 @@ describe("Wrapped Voting Escrow OLAS", function () { const account = signers[1]; // Approve owner for 1 OLAS by veOLAS - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now @@ -129,11 +129,11 @@ describe("Wrapped Voting Escrow OLAS", function () { expect(await wve.getVotes(account.address)).to.equal(0); // Try to create lock for the zero address await expect( - ve.connect(owner).createLockFor(AddressZero, oneOLABalance, lockDuration) + ve.connect(owner).createLockFor(AddressZero, oneOLASBalance, lockDuration) ).to.be.revertedWithCustomError(ve, "ZeroAddress"); // Lock for the account from the funds of the owner (approved for veOLAS) - await ve.connect(owner).createLockFor(account.address, oneOLABalance, lockDuration); + await ve.connect(owner).createLockFor(account.address, oneOLASBalance, lockDuration); // Lock end is rounded by 1 week, as implemented by design const lockEnd = await wve.lockedEnd(account.address); @@ -143,7 +143,7 @@ describe("Wrapped Voting Escrow OLAS", function () { // Get the account of the last user point const pv = await wve.getLastUserPoint(account.address); - expect(pv.balance).to.equal(oneOLABalance); + expect(pv.balance).to.equal(oneOLASBalance); // Get the number of user points for owner and compare the balance of the last point const numAccountPoints = await wve.getNumUserPoints(account.address); @@ -156,27 +156,27 @@ describe("Wrapped Voting Escrow OLAS", function () { const deployer = signers[0]; // Transfer 10 OLAS to signers[1] const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve deployer for 2 OLAS by voting escrow - await olas.approve(ve.address, twoOLABalance); + await olas.approve(ve.address, twoOLASBalance); // Approve owner for 1 OLAS by voting escrow - await olas.connect(owner).approve(ve.address, oneOLABalance); + await olas.connect(owner).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now // Try to deposit 1 OLAS for deployer without initially locked balance await expect( - ve.depositFor(deployer.address, oneOLABalance) + ve.depositFor(deployer.address, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "NoValueLocked"); // Create lock for the deployer - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Try to lock the remainder of 1 OLAS for deployer from the account that did not approve for veOLAS await expect( - ve.connect(signers[2]).depositFor(deployer.address, oneOLABalance) + ve.connect(signers[2]).depositFor(deployer.address, oneOLASBalance) ).to.be.reverted; // Try to deposit zero value for deployer @@ -190,17 +190,17 @@ describe("Wrapped Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(ve, "Overflow"); // Deposit for the deployer from the - await ve.connect(owner).depositFor(deployer.address, oneOLABalance); + await ve.connect(owner).depositFor(deployer.address, oneOLASBalance); // Check the balance of deployer (must be twice of his initial one) const balanceDeployer = await wve.balanceOf(deployer.address); - expect(balanceDeployer).to.equal(twoOLABalance); + expect(balanceDeployer).to.equal(twoOLASBalance); // Try to deposit 1 OLAS for deployer after its lock time hase expired ethers.provider.send("evm_increaseTime", [oneWeek + 1000]); ethers.provider.send("evm_mine"); await expect( - ve.depositFor(deployer.address, oneOLABalance) + ve.depositFor(deployer.address, oneOLASBalance) ).to.be.revertedWithCustomError(ve, "LockExpired"); }); }); @@ -210,11 +210,11 @@ describe("Wrapped Voting Escrow OLAS", function () { // Transfer 10 OLAS worth of OLAS to signers[1] const deployer = signers[0]; const account = signers[1]; - await olas.transfer(account.address, tenOLABalance); + await olas.transfer(account.address, tenOLASBalance); // Approve deployer and account for 1 and 10 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); - await olas.connect(account).approve(ve.address, tenOLABalance); + await olas.approve(ve.address, oneOLASBalance); + await olas.connect(account).approve(ve.address, tenOLASBalance); // Initial total supply must be 0 expect(await ve.totalSupply()).to.equal(0); @@ -223,8 +223,8 @@ describe("Wrapped Voting Escrow OLAS", function () { const lockDuration = oneWeek; // 1 week from now // Create locks for both addresses deployer and account - await ve.createLock(oneOLABalance, lockDuration); - await ve.connect(account).createLock(twoOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); + await ve.connect(account).createLock(twoOLASBalance, lockDuration); // Balance is time-based, it changes slightly every fraction of a time // Use both balances to check for the supply @@ -261,13 +261,13 @@ describe("Wrapped Voting Escrow OLAS", function () { ).to.be.revertedWithCustomError(ve, "WrongBlockNumber"); // Transfer OLAS to the user - await olas.transfer(user.address, tenOLABalance); + await olas.transfer(user.address, tenOLASBalance); // Approve user for 1 OLAS by voting escrow - await olas.connect(user).approve(ve.address, oneOLABalance); + await olas.connect(user).approve(ve.address, oneOLASBalance); // Define 1 week for the lock duration const lockDuration = oneWeek; // 1 week from now // Create locks for both addresses deployer and account - await ve.connect(user).createLock(oneOLABalance, lockDuration); + await ve.connect(user).createLock(oneOLASBalance, lockDuration); blockNumber = await ethers.provider.getBlockNumber("latest"); // Try to get past votes of a block number in the future @@ -280,22 +280,22 @@ describe("Wrapped Voting Escrow OLAS", function () { // Transfer 10 OLAS worth of OLAS to signers[1] const deployer = signers[0]; const owner = signers[1]; - await olas.transfer(owner.address, tenOLABalance); + await olas.transfer(owner.address, tenOLASBalance); // Approve signers[0] and signers[1] for 10 OLAS by voting escrow - await olas.approve(ve.address, tenOLABalance); - await olas.connect(owner).approve(ve.address, tenOLABalance); + await olas.approve(ve.address, tenOLASBalance); + await olas.connect(owner).approve(ve.address, tenOLASBalance); // Define 1 week for the lock duration let lockDuration = oneWeek; // Create and increase locks for both addresses signers[0] and signers[1] - await ve.createLock(twoOLABalance, lockDuration); - await ve.increaseAmount(oneOLABalance); + await ve.createLock(twoOLASBalance, lockDuration); + await ve.increaseAmount(oneOLASBalance); let blockNumber = await ethers.provider.getBlockNumber("latest"); - await ve.connect(owner).createLock(twoOLABalance, lockDuration); - await ve.connect(owner).increaseAmount(oneOLABalance); - await ve.connect(owner).increaseAmount(oneOLABalance); + await ve.connect(owner).createLock(twoOLASBalance, lockDuration); + await ve.connect(owner).increaseAmount(oneOLASBalance); + await ve.connect(owner).increaseAmount(oneOLASBalance); // The past votes before the lock must be zero let votesOwner = ethers.BigNumber.from(await wve.getPastVotes(owner.address, blockNumber)); @@ -333,21 +333,21 @@ describe("Wrapped Voting Escrow OLAS", function () { it("Should fail when calling a function that must be called from the original veOLAS", async function () { const wveProxy = await ethers.getContractAt("veOLAS", wve.address); await expect( - wveProxy.createLock(oneOLABalance, oneWeek) + wveProxy.createLock(oneOLASBalance, oneWeek) ).to.be.revertedWithCustomError(wve, "ImplementedIn"); }); it("Balance with a block number lower than a zero user point block number returns zero value", async function () { // Transfer 10 OLAS worth of OLAS to signers[1] const deployer = signers[0]; - await olas.transfer(deployer.address, tenOLABalance); + await olas.transfer(deployer.address, tenOLASBalance); // Approve signers[0] for 10 OLAS by voting escrow - await olas.approve(ve.address, tenOLABalance); + await olas.approve(ve.address, tenOLASBalance); const block = await ethers.provider.getBlock("latest"); // Create lock for signers[0] - await ve.createLock(twoOLABalance, oneWeek); + await ve.createLock(twoOLASBalance, oneWeek); // Try to get the balance value before the lock const balance = await wve.balanceOfAt(deployer.address, block.number); @@ -360,7 +360,7 @@ describe("Wrapped Voting Escrow OLAS", function () { const deployer = signers[0].address; const user = signers[1].address; // Approve signers[0] for 1 OLAS by voting escrow - await olas.approve(ve.address, oneOLABalance); + await olas.approve(ve.address, oneOLASBalance); // Initial total supply must be 0 expect(await wve.totalSupply()).to.equal(0); @@ -369,20 +369,20 @@ describe("Wrapped Voting Escrow OLAS", function () { const lockDuration = oneWeek; // 1 week from now // Create locks for both addresses signers[0] and signers[1] - await ve.createLock(oneOLABalance, lockDuration); + await ve.createLock(oneOLASBalance, lockDuration); // Try to call transfer-related functions for veOLAS await expect( - wve.approve(user, oneOLABalance) + wve.approve(user, oneOLASBalance) ).to.be.revertedWithCustomError(wve, "NonTransferable"); await expect( wve.allowance(deployer, user) ).to.be.revertedWithCustomError(wve, "NonTransferable"); await expect( - wve.transfer(user, oneOLABalance) + wve.transfer(user, oneOLASBalance) ).to.be.revertedWithCustomError(wve, "NonTransferable"); await expect( - wve.transferFrom(deployer, user, oneOLABalance) + wve.transferFrom(deployer, user, oneOLASBalance) ).to.be.revertedWithCustomError(wve, "NonTransferable"); // Try to call delegate-related functions for veOLAS From b3e40e49ee92fb77f1c26e935f7f2344668c1384 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 14:41:45 +0100 Subject: [PATCH 06/14] fix: VoteWeighting constructor --- contracts/VoteWeighting.sol | 5 +++-- test/VoteWeighting.js | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/VoteWeighting.js diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index d59f832..bee1005 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -96,7 +96,7 @@ contract VoteWeighting is IErrors { /// @param _ve `VotingEscrow` contract address. constructor(address _ve) { // Check for the zero address - if (_ve != address(0)) { + if (_ve == address(0)) { revert ZeroAddress(); } @@ -326,6 +326,7 @@ contract VoteWeighting is IErrors { emit NewNomineeWeight(nominee, chainId, weight, newSum); } + // TODO Shall we allow any weight change, or just set it to zero? /// @notice Change weight of nominee `addr` to `weight`. /// @param nominee Address of the nominee. /// @param chainId Chain Id. @@ -377,7 +378,7 @@ contract VoteWeighting is IErrors { uint256 power_used = voteUserPower[msg.sender]; power_used = power_used + new_slope.power - old_slope.power; voteUserPower[msg.sender] = power_used; - require(power_used >= 0 && power_used <= 10000, 'Used too much power'); + require(power_used >= 0 && power_used <= 10000, "Used too much power"); // Remove old and schedule new slope changes // Remove slope changes for old slopes diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js new file mode 100644 index 0000000..c7883ef --- /dev/null +++ b/test/VoteWeighting.js @@ -0,0 +1,43 @@ +/*global describe, context, beforeEach, it*/ + +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("Voting Escrow OLAS", function () { + let olas; + let ve; + let vw; + let signers; + const initialMint = "1000000000000000000000000"; // 1000000 + const oneWeek = 7 * 86400; + const oneOLASBalance = ethers.utils.parseEther("1"); + const twoOLASBalance = ethers.utils.parseEther("2"); + const tenOLASBalance = ethers.utils.parseEther("10"); + const AddressZero = "0x" + "0".repeat(40); + const overflowNum96 = "8" + "0".repeat(28); + + beforeEach(async function () { + const OLAS = await ethers.getContractFactory("OLAS"); + olas = await OLAS.deploy(); + await olas.deployed(); + + signers = await ethers.getSigners(); + await olas.mint(signers[0].address, initialMint); + + const VE = await ethers.getContractFactory("veOLAS"); + ve = await VE.deploy(olas.address, "Voting Escrow OLAS", "veOLAS"); + await ve.deployed(); + + const VoteWeighting = await ethers.getContractFactory("VoteWeighting"); + vw = await VoteWeighting.deploy(ve.address); + await vw.deployed(); + }); + + context("Initialization", async function () { + it("veOLAS", async function () { + // Checks for the veOLAS account + const veAddress = await vw.ve(); + expect(ve.address).to.equal(veAddress); + }); + }); +}); From 9aa89ea01446b3d27cd017b063fee5f8ca1c3d41 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 16:26:08 +0100 Subject: [PATCH 07/14] refactor: modifying errors --- contracts/VoteWeighting.sol | 68 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index bee1005..80d524e 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -3,6 +3,22 @@ pragma solidity ^0.8.23; import "./interfaces/IErrors.sol"; +interface IVEOLAS { + /// @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); +} + +error NomineeDoesNotExist(address nominee, uint256 chainId); +error NomineeAlreadyExists(address nominee, uint256 chainId); +error VoteTooOften(address voter, uint256 curTime, uint256 nextAllowedVotingTime); + struct Point { uint256 bias; uint256 slope; @@ -30,18 +46,6 @@ struct PointVoting { uint128 balance; } -interface IVEOLAS { - /// @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); -} - contract VoteWeighting is IErrors { event OwnerUpdated(address indexed owner); event NewNomineeWeight(address indexed nominee, uint256 chainId, uint256 weight, uint256 totalWeight); @@ -52,6 +56,8 @@ contract VoteWeighting is IErrors { uint256 public constant WEEK = 604800; // Cannot change weight votes more often than once in 10 days uint256 public constant WEIGHT_VOTE_DELAY = 864000; + // Max weight amount + uint256 public constant MAX_WEIGHT = 10000; // Maximum chain Id as per EVM specs uint256 public constant MAX_CHAIN_ID = type(uint64).max / 2 - 36; // veOLAS contract address @@ -169,7 +175,7 @@ contract VoteWeighting is IErrors { // Check that the nominee exists if (!mapNominees[nomineeChainId]) { - revert("Does not exist"); + revert NomineeDoesNotExist(nominee, chainId); } uint256 t = timeWeight[nomineeChainId]; @@ -222,7 +228,7 @@ contract VoteWeighting is IErrors { nomineeChainId |= chainId << 160; if (mapNominees[nomineeChainId]) { - revert("Cannot add the same nominee twice"); + revert NomineeAlreadyExists(nominee, chainId); } mapNominees[nomineeChainId] = true; @@ -346,15 +352,27 @@ contract VoteWeighting is IErrors { uint256 nomineeChainId = uint256(uint160(nominee)); // chain Id occupies no more than next 64 bits nomineeChainId |= chainId << 160; - - PointVoting memory pv = IVEOLAS(ve).getLastUserPoint(msg.sender); - uint256 slope = uint256(uint128(pv.slope)); + + uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); uint256 lock_end = IVEOLAS(ve).lockedEnd(msg.sender); uint256 next_time = (block.timestamp + WEEK) / WEEK * WEEK; - require(lock_end > next_time, "Your token lock expires too soon"); - require(weight >= 0 && weight <= 10000, "You used all your voting power"); - require(block.timestamp >= lastUserVote[msg.sender][nomineeChainId] + WEIGHT_VOTE_DELAY, "Cannot vote so often"); + // TODO: check if next_time == lock_end is ok? + // Check for the lock end expiration + if (next_time > lock_end) { + revert LockExpired(msg.sender, lock_end, next_time); + } + + // 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][nomineeChainId] + WEIGHT_VOTE_DELAY; + if (nextAllowedVotingTime > block.timestamp) { + revert VoteTooOften(msg.sender, block.timestamp, nextAllowedVotingTime); + } // Prepare old and new slopes and biases VotedSlope memory old_slope = voteUserSlopes[msg.sender][nomineeChainId]; @@ -364,21 +382,19 @@ contract VoteWeighting is IErrors { } VotedSlope memory new_slope = VotedSlope({ - slope: slope * weight / 10000, + slope: slope * weight / MAX_WEIGHT, end: lock_end, power: weight }); - // Check for the lock end expiration - if (next_time > lock_end) { - revert LockExpired(msg.sender, lock_end, next_time); - } uint256 new_bias = new_slope.slope * (lock_end - next_time); uint256 power_used = voteUserPower[msg.sender]; power_used = power_used + new_slope.power - old_slope.power; voteUserPower[msg.sender] = power_used; - require(power_used >= 0 && power_used <= 10000, "Used too much power"); + if (power_used > MAX_WEIGHT) { + revert Overflow(power_used, MAX_WEIGHT); + } // Remove old and schedule new slope changes // Remove slope changes for old slopes From 31fa67cc1340e27fc06628779031b4a6ace9b045 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Tue, 9 Apr 2024 19:20:13 +0100 Subject: [PATCH 08/14] refactor: removing weight change and making contract ownless --- contracts/VoteWeighting.sol | 175 ++++++++++++------------------------ 1 file changed, 58 insertions(+), 117 deletions(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index 80d524e..193f861 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -47,7 +47,6 @@ struct PointVoting { } contract VoteWeighting is IErrors { - event OwnerUpdated(address indexed owner); 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); @@ -62,8 +61,6 @@ contract VoteWeighting is IErrors { uint256 public constant MAX_CHAIN_ID = type(uint64).max / 2 - 36; // veOLAS contract address address public immutable ve; - // Contract owner address - address public owner; // TODO: Convert both to cyclic map // Set of (chainId | nominee) @@ -98,7 +95,7 @@ contract VoteWeighting is IErrors { // last scheduled time (next week) uint256 public timeSum; - /// @notice Contract constructor. + /// @dev Contract constructor. /// @param _ve `VotingEscrow` contract address. constructor(address _ve) { // Check for the zero address @@ -107,29 +104,11 @@ contract VoteWeighting is IErrors { } // Set initial parameters - owner = msg.sender; ve = _ve; timeSum = block.timestamp / WEEK * WEEK; } - /// @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); - } - - /// @notice Fill sum of nominee weights for the same type week-over-week for missed checkins and return the sum for the future week. + /// @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) { uint256 t = timeSum; @@ -140,11 +119,11 @@ contract VoteWeighting is IErrors { break; } t += WEEK; - uint256 d_bias = pt.slope * WEEK; - if (pt.bias > d_bias) { - pt.bias -= d_bias; - uint256 d_slope = changesSum[t]; - pt.slope -= d_slope; + 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; @@ -161,7 +140,7 @@ contract VoteWeighting is IErrors { } } - /// @notice Fill historic nominee weights week-over-week for missed checkins and return the total for the future week. + /// @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. /// @return Nominee weight. @@ -186,11 +165,11 @@ contract VoteWeighting is IErrors { break; } t += WEEK; - uint256 d_bias = pt.slope * WEEK; - if (pt.bias > d_bias) { - pt.bias -= d_bias; - uint256 d_slope = changesWeight[nomineeChainId][t]; - pt.slope -= d_slope; + uint256 dBias = pt.slope * WEEK; + if (pt.bias > dBias) { + pt.bias -= dBias; + uint256 dSlope = changesWeight[nomineeChainId][t]; + pt.slope -= dSlope; } else { pt.bias = 0; pt.slope = 0; @@ -207,7 +186,7 @@ contract VoteWeighting is IErrors { } } - /// @notice Add nominee address along with the chain Id. + /// @dev Add nominee address along with the chain Id. /// @param nominee Address of the nominee. /// @param chainId Chain Id. function addNominee(address nominee, uint256 chainId) external { @@ -234,22 +213,22 @@ contract VoteWeighting is IErrors { nomineeAccounts.push(nomineeChainId); - uint256 next_time = (block.timestamp + WEEK) / WEEK * WEEK; + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; if (timeSum == 0) { - timeSum = next_time; + timeSum = nextTime; } - timeWeight[nomineeChainId] = next_time; + timeWeight[nomineeChainId] = nextTime; emit NewNominee(nominee, chainId); } - /// @notice Checkpoint to fill data common for all nominees. + /// @dev Checkpoint to fill data common for all nominees. function checkpoint() external { _getSum(); } - /// @notice Checkpoint to fill data for both a specific nominee and common for all nominees. + /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees. /// @param nominee Address of the nominee. /// @param chainId Chain Id. function checkpointNominee(address nominee, uint256 chainId) external { @@ -257,7 +236,7 @@ contract VoteWeighting is IErrors { _getSum(); } - /// @notice Get Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18). + /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18). /// Inflation which will be received by it is inflation_rate * relativeWeight / 1e18. /// @param nominee Address of the nominee. /// @param chainId Chain Id. @@ -265,7 +244,7 @@ contract VoteWeighting is IErrors { /// @return Value of relative weight normalized to 1e18. function _nomineeRelativeWeight(address nominee, uint256 chainId, uint256 time) internal view returns (uint256) { uint256 t = time / WEEK * WEEK; - uint256 _totalSum = pointsSum[t].bias; + uint256 totalSum = pointsSum[t].bias; // Push a pair of key defining variables into one key // nominee occupies first 160 bits @@ -273,15 +252,15 @@ contract VoteWeighting is IErrors { // chain Id occupies no more than next 64 bits nomineeChainId |= chainId << 160; - if (_totalSum > 0) { - uint256 _nomineeWeight = pointsWeight[nomineeChainId][t].bias; - return 1e18 * _nomineeWeight / _totalSum; + if (totalSum > 0) { + uint256 nomineeWeight = pointsWeight[nomineeChainId][t].bias; + return 1e18 * nomineeWeight / totalSum; } else { return 0; } } - /// @notice Get Nominee relative weight (not more than 1.0) normalized to 1e18. + /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18. /// (e.g. 1.0 == 1e18). Inflation which will be received by it is /// inflation_rate * relativeWeight / 1e18. /// @param nominee Address of the nominee. @@ -292,8 +271,8 @@ contract VoteWeighting is IErrors { return _nomineeRelativeWeight(nominee, chainId, time); } - /// @notice Get nominee weight normalized to 1e18 and also fill all the unfilled values for type and nominee records. - /// @dev Any address can call, however nothing is recorded if the values are filled already. + /// @dev Get nominee weight normalized to 1e18 and also fill all the unfilled values for type and nominee records. + /// @notice Any address can call, however nothing is recorded if the values are filled already. /// @param nominee Address of the nominee. /// @param chainId Chain Id. /// @param time Relative weight at the specified timestamp in the past or present. @@ -304,44 +283,6 @@ contract VoteWeighting is IErrors { return _nomineeRelativeWeight(nominee, chainId, time); } - // TODO: Supposedly this can only bring weight to zero if something went wrong with the contract - /// @dev Change weight of `nominee` to `weight`. - /// @param nominee Address of the nominee. - /// @param chainId Chain Id. - /// @param weight New nominee weight. - function _changeNomineeWeight(address nominee, uint256 chainId, uint256 weight) internal { - // Change nominee weight - // Only needed when testing in reality - uint256 old_nomineeWeight = _getWeight(nominee, chainId); - uint256 oldSum = _getSum(); - uint256 next_time = (block.timestamp + WEEK) / WEEK * WEEK; - - // 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; - - pointsWeight[nomineeChainId][next_time].bias = weight; - timeWeight[nomineeChainId] = next_time; - - uint256 newSum = oldSum + weight - old_nomineeWeight; - pointsSum[next_time].bias = newSum; - timeSum = next_time; - - emit NewNomineeWeight(nominee, chainId, weight, newSum); - } - - // TODO Shall we allow any weight change, or just set it to zero? - /// @notice Change weight of nominee `addr` to `weight`. - /// @param nominee Address of the nominee. - /// @param chainId Chain Id. - /// @param weight New nominee weight. - function changeNomineeWeight(address nominee, uint256 chainId, uint256 weight) external { - require(msg.sender == owner, "Only owner can change nominee weight"); - _changeNomineeWeight(nominee, chainId, weight); - } - /// @notice Allocate voting power for changing pool weights. /// @param nominee Address of the nominee the `msg.sender` votes for. /// @param chainId Chain Id. @@ -354,13 +295,13 @@ contract VoteWeighting is IErrors { nomineeChainId |= chainId << 160; uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); - uint256 lock_end = IVEOLAS(ve).lockedEnd(msg.sender); - uint256 next_time = (block.timestamp + WEEK) / WEEK * WEEK; + uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); + uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; - // TODO: check if next_time == lock_end is ok? + // TODO: check if nextTime == lockEnd is ok? // Check for the lock end expiration - if (next_time > lock_end) { - revert LockExpired(msg.sender, lock_end, next_time); + if (nextTime > lockEnd) { + revert LockExpired(msg.sender, lockEnd, nextTime); } // Check for the weight number @@ -375,49 +316,49 @@ contract VoteWeighting is IErrors { } // Prepare old and new slopes and biases - VotedSlope memory old_slope = voteUserSlopes[msg.sender][nomineeChainId]; - uint256 old_bias; - if (old_slope.end > next_time) { - old_bias = old_slope.slope * (old_slope.end - next_time); + VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeChainId]; + uint256 oldBias; + if (oldSlope.end > nextTime) { + oldBias = oldSlope.slope * (oldSlope.end - nextTime); } - VotedSlope memory new_slope = VotedSlope({ + VotedSlope memory newSlope = VotedSlope({ slope: slope * weight / MAX_WEIGHT, - end: lock_end, + end: lockEnd, power: weight }); - uint256 new_bias = new_slope.slope * (lock_end - next_time); + uint256 newBias = newSlope.slope * (lockEnd - nextTime); - uint256 power_used = voteUserPower[msg.sender]; - power_used = power_used + new_slope.power - old_slope.power; - voteUserPower[msg.sender] = power_used; - if (power_used > MAX_WEIGHT) { - revert Overflow(power_used, MAX_WEIGHT); + 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 next_time - pointsWeight[nomineeChainId][next_time].bias = _maxAndSub(_getWeight(nominee, chainId) + new_bias, old_bias); - pointsSum[next_time].bias = _maxAndSub(_getSum() + new_bias, old_bias); - if (old_slope.end > next_time) { - pointsWeight[nomineeChainId][next_time].slope = _maxAndSub(pointsWeight[nomineeChainId][next_time].slope + new_slope.slope, old_slope.slope); - pointsSum[next_time].slope = _maxAndSub(pointsSum[next_time].slope + new_slope.slope, old_slope.slope); + // Schedule recording of initial slope for nextTime + pointsWeight[nomineeChainId][nextTime].bias = _maxAndSub(_getWeight(nominee, 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); + pointsSum[nextTime].slope = _maxAndSub(pointsSum[nextTime].slope + newSlope.slope, oldSlope.slope); } else { - pointsWeight[nomineeChainId][next_time].slope += new_slope.slope; - pointsSum[next_time].slope += new_slope.slope; + pointsWeight[nomineeChainId][nextTime].slope += newSlope.slope; + pointsSum[nextTime].slope += newSlope.slope; } - if (old_slope.end > block.timestamp) { + if (oldSlope.end > block.timestamp) { // Cancel old slope changes if they still didn't happen - changesWeight[nomineeChainId][old_slope.end] -= old_slope.slope; - changesSum[old_slope.end] -= old_slope.slope; + changesWeight[nomineeChainId][oldSlope.end] -= oldSlope.slope; + changesSum[oldSlope.end] -= oldSlope.slope; } // Add slope changes for new slopes - changesWeight[nomineeChainId][new_slope.end] += new_slope.slope; - changesSum[new_slope.end] += new_slope.slope; + changesWeight[nomineeChainId][newSlope.end] += newSlope.slope; + changesSum[newSlope.end] += newSlope.slope; - voteUserSlopes[msg.sender][nomineeChainId] = new_slope; + voteUserSlopes[msg.sender][nomineeChainId] = newSlope; // Record last action time lastUserVote[msg.sender][nomineeChainId] = block.timestamp; From 7feb9891592f53710b0da9c740a169149eb6b925 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Wed, 10 Apr 2024 14:53:37 +0100 Subject: [PATCH 09/14] test: adding 90% of coverage --- .github/workflows/workflow.yaml | 5 + .solcover.js | 8 +- contracts/VoteWeighting.sol | 224 +++++++++++++++-------- test/VoteWeighting.js | 302 +++++++++++++++++++++++++++++++- 4 files changed, 458 insertions(+), 81 deletions(-) diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 91851ac..bad87a9 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -1,4 +1,9 @@ name: Workflow + +concurrency: + cancel-in-progress: true + group: ${{github.workflow}}-${{github.ref}} + on: push: branches: diff --git a/.solcover.js b/.solcover.js index 706e13d..4e7bed3 100644 --- a/.solcover.js +++ b/.solcover.js @@ -1,10 +1,16 @@ module.exports = { skipFiles: ["bridges/test/FxChildTunnel.sol", "bridges/test/ChildMockERC20.sol", - "bridges/test/MockTimelock.sol", + "bridges/test/FxRootMock.sol", "bridges/test/HomeMediatorTest.sol", "bridges/test/MockAMBMediator.sol", + "bridges/test/MockL2Relayer.sol", + "bridges/test/MockTimelock.sol", + "bridges/test/WormholeL1Receiver.sol", + "bridges/test/WormholeL1Sender.sol", + "bridges/test/WormholeL2ReceiverL1Sender.sol", "test/BridgeSetup.sol", + "test/BrokenERC20.sol", "test/SafeSetup.sol", "multisigs/test/MockTimelockCM.sol", "multisigs/test/MockTreasury.sol", diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index 193f861..9ca6a52 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -62,11 +62,11 @@ contract VoteWeighting is IErrors { // veOLAS contract address address public immutable ve; - // TODO: Convert both to cyclic map + // TODO: Convert both to cyclic map? // Set of (chainId | nominee) - uint256[] public nomineeAccounts; - // Mapping of (chainId | nominee) - mapping(uint256 => bool) public mapNominees; + 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; @@ -106,38 +106,36 @@ contract VoteWeighting is IErrors { // Set initial parameters ve = _ve; timeSum = block.timestamp / WEEK * WEEK; + setNominees.push(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; - if (t > 0) { - 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; - } + 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; - } else { - return 0; } + return pt.bias; } /// @dev Fill historic nominee weights week-over-week for missed checkins and return the total for the future week. @@ -153,37 +151,34 @@ contract VoteWeighting is IErrors { nomineeChainId |= chainId << 160; // Check that the nominee exists - if (!mapNominees[nomineeChainId]) { + if (mapNomineeIds[nomineeChainId] == 0) { revert NomineeDoesNotExist(nominee, chainId); } + // t is always > 0 as it is set during the addNominee() call uint256 t = timeWeight[nomineeChainId]; - if (t > 0) { - Point memory pt = pointsWeight[nomineeChainId][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[nomineeChainId][t]; - pt.slope -= dSlope; - } else { - pt.bias = 0; - pt.slope = 0; - } - - pointsWeight[nomineeChainId][t] = pt; - if (t > block.timestamp) { - timeWeight[nomineeChainId] = t; - } + Point memory pt = pointsWeight[nomineeChainId][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[nomineeChainId][t]; + pt.slope -= dSlope; + } else { + pt.bias = 0; + pt.slope = 0; + } + + pointsWeight[nomineeChainId][t] = pt; + if (t > block.timestamp) { + timeWeight[nomineeChainId] = t; } - return pt.bias; - } else { - return 0; } + return pt.bias; } /// @dev Add nominee address along with the chain Id. @@ -196,7 +191,10 @@ contract VoteWeighting is IErrors { } // Check for the chain Id - if (chainId == 0 || chainId > MAX_CHAIN_ID) { + if (chainId == 0) { + revert ZeroValue(); + } + else if (chainId > MAX_CHAIN_ID) { revert Overflow(chainId, MAX_CHAIN_ID); } @@ -206,18 +204,15 @@ contract VoteWeighting is IErrors { // chain Id occupies no more than next 64 bits nomineeChainId |= chainId << 160; - if (mapNominees[nomineeChainId]) { + // Check for the nominee existence + if (mapNomineeIds[nomineeChainId] > 0) { revert NomineeAlreadyExists(nominee, chainId); } - mapNominees[nomineeChainId] = true; - - nomineeAccounts.push(nomineeChainId); + mapNomineeIds[nomineeChainId] = setNominees.length; + // Push the nominee into the list + setNominees.push(nomineeChainId); uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; - - if (timeSum == 0) { - timeSum = nextTime; - } timeWeight[nomineeChainId] = nextTime; emit NewNominee(nominee, chainId); @@ -241,8 +236,8 @@ contract VoteWeighting is IErrors { /// @param nominee Address of the nominee. /// @param chainId Chain Id. /// @param time Relative weight at the specified timestamp in the past or present. - /// @return Value of relative weight normalized to 1e18. - function _nomineeRelativeWeight(address nominee, uint256 chainId, uint256 time) internal view returns (uint256) { + /// @return weight Value of relative weight normalized to 1e18. + function _nomineeRelativeWeight(address nominee, uint256 chainId, uint256 time) internal view returns (uint256 weight) { uint256 t = time / WEEK * WEEK; uint256 totalSum = pointsSum[t].bias; @@ -254,9 +249,7 @@ contract VoteWeighting is IErrors { if (totalSum > 0) { uint256 nomineeWeight = pointsWeight[nomineeChainId][t].bias; - return 1e18 * nomineeWeight / totalSum; - } else { - return 0; + weight = 1e18 * nomineeWeight / totalSum; } } @@ -283,7 +276,7 @@ contract VoteWeighting is IErrors { return _nomineeRelativeWeight(nominee, chainId, time); } - /// @notice Allocate voting power for changing pool weights. + /// @dev Allocate voting power for changing pool weights. /// @param nominee Address of the nominee the `msg.sender` votes for. /// @param chainId Chain Id. /// @param weight Weight for a nominee in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0. @@ -293,14 +286,13 @@ contract VoteWeighting is IErrors { uint256 nomineeChainId = uint256(uint160(nominee)); // chain Id occupies no more than next 64 bits nomineeChainId |= chainId << 160; - + uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope)); uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender); uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK; - // TODO: check if nextTime == lockEnd is ok? // Check for the lock end expiration - if (nextTime > lockEnd) { + if (nextTime >= lockEnd) { revert LockExpired(msg.sender, lockEnd, nextTime); } @@ -366,7 +358,7 @@ contract VoteWeighting is IErrors { emit VoteForNominee(msg.sender, nominee, chainId, weight); } - /// @notice Allocate voting power for changing pool weights in batch. + /// @dev Allocate voting power for changing pool weights in batch. /// @param nominees Set of nominees 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. @@ -389,7 +381,7 @@ contract VoteWeighting is IErrors { return a > b ? a - b : 0; } - /// @notice Get current nominee weight. + /// @dev Get current nominee weight. /// @param nominee Address of the nominee. /// @param chainId Chain Id. /// @return Nominee weight. @@ -403,9 +395,93 @@ contract VoteWeighting is IErrors { return pointsWeight[nomineeChainId][timeWeight[nomineeChainId]].bias; } - /// @notice Get sum of nominee weights. + /// @dev Get sum of nominee weights. /// @return Sum of nominee weights. function getWeightsSum() external view returns (uint256) { return pointsSum[timeSum].bias; } + + /// @dev Get the 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 the nominee Id in the global nominees set. + /// @param nominee Nominee address. + /// @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]; + } + + /// @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) { + // 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); + } + + uint256 nomineeChainId = setNominees[id]; + // Extract the nominee address + nominee = address(uint160(uint256(nomineeChainId))); + // Extract chain Id + chainId = nomineeChainId >> 160; + } + + /// @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 numNominees Number of nominees to get. + /// @return nominees Set of nominee addresses. + /// @return chainIds Set of corresponding chain Ids. + function getNominees( + uint256 startId, + uint256 numNominees + ) external view returns (address[] memory nominees, uint256[] memory chainIds) + { + // 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 + nominees = new address[](numNominees); + chainIds = new uint256[](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; + } + } } \ No newline at end of file diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index c7883ef..3aa26be 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -2,19 +2,27 @@ const { expect } = require("chai"); const { ethers } = require("hardhat"); +const helpers = require("@nomicfoundation/hardhat-network-helpers"); describe("Voting Escrow OLAS", function () { let olas; let ve; let vw; let signers; - const initialMint = "1000000000000000000000000"; // 1000000 + let deployer; + const initialMint = "1000000000000000000000000"; // 1_000_000 const oneWeek = 7 * 86400; + const oneYear = 365 * 86400; + const chainId = 1; + const maxVoteWeight = 10000; + const E18 = 10**18; const oneOLASBalance = ethers.utils.parseEther("1"); - const twoOLASBalance = ethers.utils.parseEther("2"); - const tenOLASBalance = ethers.utils.parseEther("10"); - const AddressZero = "0x" + "0".repeat(40); - const overflowNum96 = "8" + "0".repeat(28); + const AddressZero = ethers.constants.AddressZero; + const maxU256 = ethers.constants.MaxUint256; + + function getNextTime(ts) { + return Math.floor((ts + oneWeek) / oneWeek) * oneWeek; + } beforeEach(async function () { const OLAS = await ethers.getContractFactory("OLAS"); @@ -22,7 +30,8 @@ describe("Voting Escrow OLAS", function () { await olas.deployed(); signers = await ethers.getSigners(); - await olas.mint(signers[0].address, initialMint); + deployer = signers[0]; + await olas.mint(deployer.address, initialMint); const VE = await ethers.getContractFactory("veOLAS"); ve = await VE.deploy(olas.address, "Voting Escrow OLAS", "veOLAS"); @@ -34,10 +43,291 @@ describe("Voting Escrow OLAS", function () { }); context("Initialization", async function () { + it("Should fail when deploying with the zero address", async function () { + const VoteWeighting = await ethers.getContractFactory("VoteWeighting"); + await expect( + VoteWeighting.deploy(AddressZero) + ).to.be.revertedWithCustomError(vw, "ZeroAddress"); + }); + it("veOLAS", async function () { // Checks for the veOLAS account const veAddress = await vw.ve(); expect(ve.address).to.equal(veAddress); }); }); + + context("Adding nominees", async function () { + it("Should fail with wrong nominee params", async function () { + // 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) + ).to.be.revertedWithCustomError(vw, "ZeroAddress"); + + // Try to add a zero chain Id + await expect( + vw.addNominee(signers[1].address, 0) + ).to.be.revertedWithCustomError(vw, "ZeroValue"); + + // Try to add an overflow chain Id + let overflowChainId = await vw.MAX_CHAIN_ID(); + overflowChainId = overflowChainId.add(1); + await expect( + vw.addNominee(signers[1].address, overflowChainId) + ).to.be.revertedWithCustomError(vw, "Overflow"); + }); + + it("Add nominee", async function () { + // 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); + + // Try to add the same nominee + await expect( + vw.addNominee(signers[1].address, chainId) + ).to.be.revertedWithCustomError(vw, "NomineeAlreadyExists"); + + // Check the nominee setup + const numNominees = await vw.getNumNominees(); + expect(numNominees).to.equal(1); + + const nomineeChainId = await vw.getNominee(1); + expect(nomineeChainId.nominee).to.equal(signers[1].address); + 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); + + let nomineeId = await vw.getNomineeId(signers[1].address, chainId); + expect(nomineeId).to.equal(1); + + // Check the nominee Id of a nonexistent nominee + nomineeId = await vw.getNomineeId(signers[1].address, chainId + 1); + expect(nomineeId).to.equal(0); + }); + + it("Get nominees", async function () { + // 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); + + // Try to get the zero-th nominees + await expect( + vw.getNominee(0) + ).to.be.revertedWithCustomError(vw, "ZeroValue"); + await expect( + vw.getNominees(1, 0) + ).to.be.revertedWithCustomError(vw, "ZeroValue"); + await expect( + vw.getNominees(0, 1) + ).to.be.revertedWithCustomError(vw, "ZeroValue"); + + // Try to get the nonexistent nominee + await expect( + vw.getNominee(2) + ).to.be.revertedWithCustomError(vw, "Overflow"); + await expect( + vw.getNominees(2, 1) + ).to.be.revertedWithCustomError(vw, "Overflow"); + await expect( + vw.getNominees(1, 2) + ).to.be.revertedWithCustomError(vw, "Overflow"); + }); + }); + + context("Voting", async function () { + it("Should fail with wrong input arguments", async function () { + // Add a nominee + let nominee = signers[1].address; + await vw.addNominee(nominee, chainId); + + // Approve OLAS for veOLAS + await olas.approve(ve.address, oneOLASBalance); + + // Take a snapshot of the current state of the blockchain + const snapshot = await helpers.takeSnapshot(); + + // Lock one OLAS into veOLAS for one week + await ve.createLock(oneOLASBalance, oneWeek); + + // Try to vote for the nominee when the lock is about to expire + await expect( + vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight) + ).to.be.revertedWithCustomError(vw, "LockExpired"); + + // Restore to the state of the snapshot + await snapshot.restore(); + + // Lock one OLAS into veOLAS for one year + await ve.createLock(oneOLASBalance, oneYear); + + // Try to vote for the nominee with the bigger weight + await expect( + vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight + 1) + ).to.be.revertedWithCustomError(vw, "Overflow"); + + // Vote for the nominee + await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight); + + // Try to vote for the same nominee again within a defined period + await expect( + vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight) + ).to.be.revertedWithCustomError(vw, "VoteTooOften"); + + // Try to vote for another nominee with all the voting power used + nominee = signers[2].address; + await vw.addNominee(nominee, chainId); + await expect( + vw.voteForNomineeWeights(nominee, chainId, 1) + ).to.be.revertedWithCustomError(vw, "Overflow"); + + // Voting with the zero power is possible + await vw.voteForNomineeWeights(nominee, chainId, 0); + + // Try to checkpoint nominee that does not exist + await expect( + vw.checkpointNominee(nominee, chainId + 1) + ).to.be.revertedWithCustomError(vw, "NomineeDoesNotExist"); + + // Try to vote batch with incorrect array lengths + await expect( + vw.voteForNomineeWeightsBatch([nominee], [], []) + ).to.be.revertedWithCustomError(vw, "WrongArrayLength"); + await expect( + vw.voteForNomineeWeightsBatch([nominee], [chainId], []) + ).to.be.revertedWithCustomError(vw, "WrongArrayLength"); + await expect( + vw.voteForNomineeWeightsBatch([nominee], [], [maxVoteWeight]) + ).to.be.revertedWithCustomError(vw, "WrongArrayLength"); + await expect( + vw.voteForNomineeWeightsBatch([], [chainId], [maxVoteWeight]) + ).to.be.revertedWithCustomError(vw, "WrongArrayLength"); + }); + + it("Vote for the nominees separately", async function () { + // Lock one OLAS into veOLAS + await olas.approve(ve.address, oneOLASBalance); + await ve.createLock(oneOLASBalance, oneYear); + + // Add a nominee + const nominee = signers[1].address; + await vw.addNominee(nominee, chainId); + + // Get the next point timestamp where votes are written after voting + const block = await ethers.provider.getBlock("latest"); + const nextTime = getNextTime(block.timestamp); + + // Make sure the initial weight is zero + let weight = await vw.nomineeRelativeWeight(nominee, chainId, block.timestamp); + expect(weight).to.equal(0); + weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); + expect(weight).to.equal(0); + + // Vote for the nominee + await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight / 2); + + + // Add one more nominee + const nominee2 = signers[2].address; + await vw.addNominee(nominee2, chainId); + + // Make sure the initial weight is zero + weight = await vw.nomineeRelativeWeight(nominee2, chainId, nextTime); + expect(weight).to.equal(0); + + // Vote for another nominee + await vw.voteForNomineeWeights(nominee2, chainId, maxVoteWeight / 2); + + + // Check the current nominee weight + weight = await vw.getNomineeWeight(nominee, chainId); + expect(weight).to.be.greaterThan(0); + // Check the sum of nominee weights + const sumWeights = await vw.getWeightsSum(); + expect(sumWeights).to.be.greaterThan(0); + + // Check relative weights that must represent a half for each + weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); + expect(Number(weight) / E18).to.equal(0.5); + weight = await vw.nomineeRelativeWeight(nominee2, chainId, nextTime); + expect(Number(weight) / E18).to.equal(0.5); + + // Write nominee weight and try to get one from the distant future + weight = await vw.callStatic.nomineeRelativeWeightWrite(nominee, chainId, nextTime * 2); + expect(weight).to.equal(0); + + // Checkpoint and checkpoint nominee + await vw.checkpoint(); + await vw.checkpointNominee(nominee, chainId); + }); + + it("Vote for the nominee after some time", async function () { + // Take a snapshot of the current state of the blockchain + const snapshot = await helpers.takeSnapshot(); + + // Lock one OLAS into veOLAS + await olas.approve(ve.address, oneOLASBalance); + await ve.createLock(oneOLASBalance, oneYear); + + // Add a nominee + const nominee = signers[1].address; + await vw.addNominee(nominee, chainId); + + // Wait for several weeks + await helpers.time.increase(oneWeek * 3); + + // Vote for the nominee + await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight); + + // Get the next point timestamp where votes are written after voting + const block = await ethers.provider.getBlock("latest"); + const nextTime = getNextTime(block.timestamp); + + // Check relative weights that must represent a half for each + weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); + expect(Number(weight) / E18).to.equal(1); + + // Restore to the state of the snapshot + await snapshot.restore(); + }); + + it("Batch vote for the nominees", async function () { + // Lock one OLAS into veOLAS + await olas.approve(ve.address, oneOLASBalance); + await ve.createLock(oneOLASBalance, oneYear); + + // Add nominees + const numNominees = 2; + const 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]); + } + + // Get the next point timestamp where votes are written after voting + const block = await ethers.provider.getBlock("latest"); + const nextTime = getNextTime(block.timestamp); + + // Vote for the nominees in batch + const voteWeights = new Array(2).fill(maxVoteWeight / 2); + await vw.voteForNomineeWeightsBatch(nominees, chainIds, voteWeights); + + // Check weights that must represent a half for each + for (let i = 0; i < numNominees; i++) { + const weight = await vw.nomineeRelativeWeight(nominees[i], chainIds[i], nextTime); + expect(Number(weight) / E18).to.equal(0.5); + } + }); + }); }); From 2c337c9b6e6889beee2062660c36cd16a786e8e3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Wed, 10 Apr 2024 16:38:14 +0100 Subject: [PATCH 10/14] chore: linter --- test/VoteWeighting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index 3aa26be..302e2d6 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -295,7 +295,7 @@ describe("Voting Escrow OLAS", function () { const nextTime = getNextTime(block.timestamp); // Check relative weights that must represent a half for each - weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); + const weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); expect(Number(weight) / E18).to.equal(1); // Restore to the state of the snapshot From 0ec37e6a02415eaa22696e52a3ed1c2f4b43bd86 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Wed, 10 Apr 2024 17:57:02 +0100 Subject: [PATCH 11/14] test: 100% coverage --- test/VoteWeighting.js | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index 302e2d6..f46ecc7 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -329,5 +329,90 @@ describe("Voting Escrow OLAS", function () { expect(Number(weight) / E18).to.equal(0.5); } }); + + it("Voting several times week after week", async function () { + // Take a snapshot of the current state of the blockchain + const snapshot = await helpers.takeSnapshot(); + + // Lock one OLAS into veOLAS + await olas.approve(ve.address, oneOLASBalance); + await ve.createLock(oneOLASBalance, oneYear); + + // Add a nominee + const nominee = signers[1].address; + await vw.addNominee(nominee, chainId); + + // Vote for the nominee + await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight); + + // Wait for next two weeks (must pass 10 days where one cannot vote) + await helpers.time.increase(oneWeek * 2); + + // Vote for the nominee again + await vw.voteForNomineeWeights(nominee, chainId, maxVoteWeight); + + // Get the next point timestamp where votes are written after voting + const block = await ethers.provider.getBlock("latest"); + const nextTime = getNextTime(block.timestamp); + + // Check relative weights that must represent a half for each + const weight = await vw.nomineeRelativeWeight(nominee, chainId, nextTime); + expect(Number(weight) / E18).to.equal(1); + + // Restore to the state of the snapshot + await snapshot.restore(); + }); + + it("Voting with veOLAS lock changing", async function () { + // Take a snapshot of the current state of the blockchain + const snapshot = await helpers.takeSnapshot(); + + // Add nominees + const numNominees = 2; + const nominees = [signers[2].address, signers[3].address]; + for (let i = 0; i < numNominees; i++) { + await vw.addNominee(nominees[i], chainId); + } + + // Lock one OLAS into veOLAS by deployer and another account + await olas.approve(ve.address, oneOLASBalance); + await ve.createLock(oneOLASBalance, oneYear); + const user = signers[1]; + await olas.transfer(user.address, oneOLASBalance); + await olas.connect(user).approve(ve.address, oneOLASBalance); + await ve.connect(user).createLock(oneOLASBalance, oneYear); + + // Vote for the nominee by the deployer + await vw.voteForNomineeWeights(nominees[0], chainId, maxVoteWeight); + // Vote for the nominee by the user + await vw.connect(user).voteForNomineeWeights(nominees[1], chainId, maxVoteWeight); + + // Deployer increases the OLAS amount in veOLAS + await olas.approve(ve.address, oneOLASBalance); + await ve.increaseAmount(oneOLASBalance); + + // Wait for several weeks + await helpers.time.increase(oneWeek * 3); + + // Vote for the nominee by the deployer + await vw.voteForNomineeWeights(nominees[0], chainId, maxVoteWeight); + // Vote for the nominee by the user + await vw.connect(user).voteForNomineeWeights(nominees[1], chainId, maxVoteWeight); + + // Get the next point timestamp where votes are written after voting + let block = await ethers.provider.getBlock("latest"); + let nextTime = getNextTime(block.timestamp); + + // Check relative weights that must represent a half for each + const weights = [ + await vw.nomineeRelativeWeight(nominees[0], chainId, nextTime), + await vw.nomineeRelativeWeight(nominees[1], chainId, nextTime) + ]; + // nominees[0] weight: 666666666680682666, nominees[1] weight: 333333333319317333; the ratio is 2:1 + expect(Number(weights[0]) / E18).to.be.greaterThan(Number(weights[1]) / E18); + + // Restore to the state of the snapshot + await snapshot.restore(); + }); }); }); From 73d41e61c1fd72e033a50356fbdc10c010c3f3ee Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Thu, 11 Apr 2024 10:53:47 +0100 Subject: [PATCH 12/14] test: more testing --- test/VoteWeighting.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/VoteWeighting.js b/test/VoteWeighting.js index f46ecc7..b6442c2 100644 --- a/test/VoteWeighting.js +++ b/test/VoteWeighting.js @@ -115,10 +115,6 @@ describe("Voting Escrow OLAS", function () { }); it("Get nominees", async function () { - // 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); @@ -143,6 +139,19 @@ describe("Voting Escrow OLAS", function () { await expect( vw.getNominees(1, 2) ).to.be.revertedWithCustomError(vw, "Overflow"); + + // Add one more nominee + await vw.addNominee(signers[1].address, chainId + 1); + // Try to get the nonexistent nominee + await expect( + vw.getNominee(3) + ).to.be.revertedWithCustomError(vw, "Overflow"); + await expect( + vw.getNominees(2, 2) + ).to.be.revertedWithCustomError(vw, "Overflow"); + await expect( + vw.getNominees(1, 3) + ).to.be.revertedWithCustomError(vw, "Overflow"); }); }); From 039505e0cfbd3b645328205b25f6358174af2dfe Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Thu, 11 Apr 2024 19:41:33 +0100 Subject: [PATCH 13/14] chore: small constant modification --- contracts/VoteWeighting.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/VoteWeighting.sol b/contracts/VoteWeighting.sol index 9ca6a52..9256f72 100644 --- a/contracts/VoteWeighting.sol +++ b/contracts/VoteWeighting.sol @@ -52,11 +52,11 @@ contract VoteWeighting is IErrors { event NewNominee(address nominee, uint256 chainId); // 7 * 86400 seconds - all future times are rounded by week - uint256 public constant WEEK = 604800; + uint256 public constant WEEK = 604_800; // Cannot change weight votes more often than once in 10 days - uint256 public constant WEIGHT_VOTE_DELAY = 864000; + uint256 public constant WEIGHT_VOTE_DELAY = 864_000; // Max weight amount - uint256 public constant MAX_WEIGHT = 10000; + 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; // veOLAS contract address From 53588d90893485cd8693aba0c223119566c88d3b Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 12 Apr 2024 13:52:04 +0100 Subject: [PATCH 14/14] chore: hardhat update --- package.json | 2 +- yarn.lock | 126 +++++++++++++++++++++++++-------------------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index c71582b..b4f6697 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.21.0", + "hardhat": "^2.22.2", "@typechain/hardhat": "^9.1.0", "ethers": "^5.7.2", "@typechain/ethers-v5": "^11.1.2", diff --git a/yarn.lock b/yarn.lock index 76ffe7e..3f7af79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -666,65 +666,65 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/edr-darwin-arm64@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.2.1.tgz#10c1a07add192583ce8b2d4cc93439f52b390a41" - integrity sha512-aMYaRaZVQ/TmyNJIoXf1bU4k0zfinaL9Sy1day4yGlL6eiQPFfRGj9W6TZaZIoYG0XTx/mQWD7dkXJ7LdrleJA== - -"@nomicfoundation/edr-darwin-x64@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.2.1.tgz#eaa29d2ba9f91ddb5f59b872c5a54f94a6fe3095" - integrity sha512-ma0SLcjHm5L3nPHcKFJB0jv/gKGSKaxr5Z65rurX/eaYUQJ7YGMsb8er9bSCo9rjzOtxf4FoPj3grL3zGpOj8A== - -"@nomicfoundation/edr-linux-arm64-gnu@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.2.1.tgz#8149db0d742157405effe82d485ea9bfefddc795" - integrity sha512-NX3G4pBhRitWrjSGY3HTyCq3wKSm5YqrKVOCNQGl9/jcjSovqxlgzFMiTx4YZCzGntfJ/1om9AI84OWxYJjoDw== - -"@nomicfoundation/edr-linux-arm64-musl@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.2.1.tgz#7d53afe5607eb406d199a199d00209a6304ff07b" - integrity sha512-gdQ3QHkt9XRkdtOGQ8fMwS11MXdjLeZgLrqoial4V4qtMaamIMMhVczK+VEvUhD8p7G4BVmp6kmkvcsthmndmw== - -"@nomicfoundation/edr-linux-x64-gnu@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.2.1.tgz#b762c95368fcb88bbbabba4d8be5380f38967413" - integrity sha512-OqabFY37vji6mYbLD9CvG28lja68czeVw58oWByIhFV3BpBu/cyP1oAbhzk3LieylujabS3Ekpvjw2Tkf0A9RQ== - -"@nomicfoundation/edr-linux-x64-musl@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.2.1.tgz#522448c42bff7d2abd52ddcf11ae6ca3dfdd6db4" - integrity sha512-vHfFFK2EPISuQUQge+bdjXamb0EUjfl8srYSog1qfiwyLwLeuSbpyyFzDeITAgPpkkFuedTfJW553K0Hipspyg== - -"@nomicfoundation/edr-win32-arm64-msvc@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.2.1.tgz#ccfa443c274e49de93016a1060be810096dc6f1d" - integrity sha512-K/mui67RCKxghbSyvhvW3rvyVN1pa9M1Q9APUx1PtWjSSdXDFpqEY1NYsv2syb47Ca8ObJwVMF+LvnB6GvhUOQ== - -"@nomicfoundation/edr-win32-ia32-msvc@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.2.1.tgz#822b19d3e67d6dcfa5394cb6a4d55d8bab1b2f26" - integrity sha512-HHK0mXEtjvfjJrJlqcYgQCy3lZIXS1KNl2GaP8bwEIuEwx++XxXs/ThLjPepM1nhCGICij8IGy7p3KrkzRelsw== - -"@nomicfoundation/edr-win32-x64-msvc@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.2.1.tgz#7b56ff742b2724779cc9f3385815b394f76de8df" - integrity sha512-FY4eQJdj1/y8ST0RyQycx63yr+lvdYNnUkzgWf4X+vPH1lOhXae+L2NDcNCQlTDAfQcD6yz0bkBUkLrlJ8pTww== - -"@nomicfoundation/edr@^0.2.0": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.2.1.tgz#a3d2a542dcd5dc5a8d757116d52baea05f370531" - integrity sha512-Dleau3ItHJh2n85G2J6AIPBoLgu/mOWkmrh26z3VsJE2tp/e00hUk/dqz85ncsVcBYEc6/YOn/DomWu0wSF9tQ== +"@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== optionalDependencies: - "@nomicfoundation/edr-darwin-arm64" "0.2.1" - "@nomicfoundation/edr-darwin-x64" "0.2.1" - "@nomicfoundation/edr-linux-arm64-gnu" "0.2.1" - "@nomicfoundation/edr-linux-arm64-musl" "0.2.1" - "@nomicfoundation/edr-linux-x64-gnu" "0.2.1" - "@nomicfoundation/edr-linux-x64-musl" "0.2.1" - "@nomicfoundation/edr-win32-arm64-msvc" "0.2.1" - "@nomicfoundation/edr-win32-ia32-msvc" "0.2.1" - "@nomicfoundation/edr-win32-x64-msvc" "0.2.1" + "@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/ethereumjs-common@4.0.4": version "4.0.4" @@ -3177,14 +3177,14 @@ hardhat-tracer@^2.8.1: debug "^4.3.4" ethers "^5.6.1" -hardhat@^2.21.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.21.0.tgz#2e23126310a6c77cd7e149e6af1dd67626b7a74f" - integrity sha512-8DlJAVJDEVHaV1sh9FLuKLLgCFv9EAJ+M+8IbjSIPgoeNo3ss5L1HgGBMfnI88c7OzMEZkdcuyGoobFeK3Orqw== +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== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/edr" "^0.2.0" + "@nomicfoundation/edr" "^0.3.1" "@nomicfoundation/ethereumjs-common" "4.0.4" "@nomicfoundation/ethereumjs-tx" "5.0.4" "@nomicfoundation/ethereumjs-util" "9.0.4"