From 3b4052634c6a2dcb125dda278a5d6cdbc02d1881 Mon Sep 17 00:00:00 2001 From: danilo neves cruz Date: Mon, 9 Oct 2023 17:31:33 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20vote:=20get=20voting=20power=20from?= =?UTF-8?q?=20velodrome=20and=20extra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/twenty-peas-hug.md | 5 + .gas-snapshot | 3 +- contracts/periphery/VotePreviewer.sol | 66 +++ deploy/Previewers.ts | 27 +- deployments/optimism/ExtraLending.json | 604 +++++++++++++++++++++++++ hardhat.config.ts | 2 + test/VotePreviewer.t.sol | 33 ++ 7 files changed, 738 insertions(+), 2 deletions(-) create mode 100644 .changeset/twenty-peas-hug.md create mode 100644 contracts/periphery/VotePreviewer.sol create mode 100644 deployments/optimism/ExtraLending.json create mode 100644 test/VotePreviewer.t.sol diff --git a/.changeset/twenty-peas-hug.md b/.changeset/twenty-peas-hug.md new file mode 100644 index 000000000..f2affe837 --- /dev/null +++ b/.changeset/twenty-peas-hug.md @@ -0,0 +1,5 @@ +--- +"@exactly/protocol": patch +--- + +✨ vote: get voting power from velodrome and extra diff --git a/.gas-snapshot b/.gas-snapshot index ed1ca9c91..98648f3af 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -400,4 +400,5 @@ SwapperTest:testSwapWithKeepAmount() (gas: 201230) SwapperTest:testSwapWithKeepEqualToValue() (gas: 41783) SwapperTest:testSwapWithKeepHigherThanValue() (gas: 41804) SwapperTest:testSwapWithPermit() (gas: 566299) -SwapperTest:testSwapWithPermit2() (gas: 560666) \ No newline at end of file +SwapperTest:testSwapWithPermit2() (gas: 560666) +VotePreviewerTest:testExternalVotes() (gas: 97572) \ No newline at end of file diff --git a/contracts/periphery/VotePreviewer.sol b/contracts/periphery/VotePreviewer.sol new file mode 100644 index 000000000..47300a3e6 --- /dev/null +++ b/contracts/periphery/VotePreviewer.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol"; + +/// @title Vote Previewer +/// @notice Contract to be consumed by voting strategies. +contract VotePreviewer { + using FixedPointMathLib for uint256; + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable exa; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IPool public immutable pool; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IERC20 public immutable gauge; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IExtraLending public immutable extraLending; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + uint256 public immutable extraReserveId; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address exa_, IPool pool_, IERC20 gauge_, IExtraLending extraLending_, uint256 extraReserveId_) { + exa = exa_; + pool = pool_; + gauge = gauge_; + extraLending = extraLending_; + extraReserveId = extraReserveId_; + } + + function externalVotes(address account) external view returns (uint256 votes) { + uint256 liquidity = pool.balanceOf(account) + gauge.balanceOf(account); + votes += liquidity.mulDivDown(exa == pool.token0() ? pool.reserve0() : pool.reserve1(), pool.totalSupply()); + + uint256[] memory reserveIds = new uint256[](1); + reserveIds[0] = extraReserveId; + IExtraLending.PositionStatus[] memory e = extraLending.getPositionStatus(reserveIds, account); + votes += extraLending.exchangeRateOfReserve(extraReserveId).mulWadDown(e[0].eTokenStaked + e[0].eTokenUnStaked); + } +} + +interface IPool is IERC20 { + function token0() external view returns (address); + + function reserve0() external view returns (uint256); + + function reserve1() external view returns (uint256); +} + +interface IExtraLending { + struct PositionStatus { + uint256 reserveId; + address user; + uint256 eTokenStaked; + uint256 eTokenUnStaked; + uint256 liquidity; + } + + function getPositionStatus( + uint256[] memory reserveIds, + address account + ) external view returns (PositionStatus[] memory); + + function exchangeRateOfReserve(uint256 reserveId) external view returns (uint256); +} diff --git a/deploy/Previewers.ts b/deploy/Previewers.ts index 8de57d9e7..767713da5 100644 --- a/deploy/Previewers.ts +++ b/deploy/Previewers.ts @@ -2,16 +2,34 @@ import type { DeployFunction } from "hardhat-deploy/types"; import validateUpgrade from "./.utils/validateUpgrade"; const func: DeployFunction = async ({ + network: { + config: { + finance: { periphery }, + }, + }, ethers: { constants: { AddressZero }, }, deployments: { deploy, get, getOrNull }, getNamedAccounts, }) => { - const [{ address: auditor }, { address: debtManager }, { address: priceFeedETH }, { deployer }] = await Promise.all([ + const [ + { address: auditor }, + { address: debtManager }, + { address: exa }, + { address: exaPool }, + { address: exaGauge }, + { address: priceFeedETH }, + { address: extraLending }, + { deployer }, + ] = await Promise.all([ get("Auditor"), get("DebtManager"), + get("EXA"), + getOrNull("EXAPool").then((d) => d ?? { address: AddressZero }), + getOrNull("EXAGauge").then((d) => d ?? { address: AddressZero }), getOrNull("PriceFeedETH").then((d) => d ?? { address: AddressZero }), + getOrNull("ExtraLending").then((d) => d ?? { address: AddressZero }), getNamedAccounts(), ]); await validateUpgrade("Previewer", { args: [auditor, priceFeedETH], envKey: "PREVIEWER" }, async (name, opts) => @@ -25,6 +43,13 @@ const func: DeployFunction = async ({ log: true, }), ); + + await validateUpgrade( + "VotePreviewer", + { args: [exa, exaPool, exaGauge, extraLending, periphery.extraReserve], envKey: "VOTE_PREVIEWER" }, + async (name, opts) => + deploy(name, { ...opts, proxy: { proxyContract: "TransparentUpgradeableProxy" }, from: deployer, log: true }), + ); }; func.tags = ["Previewers"]; diff --git a/deployments/optimism/ExtraLending.json b/deployments/optimism/ExtraLending.json new file mode 100644 index 000000000..8027b7cc1 --- /dev/null +++ b/deployments/optimism/ExtraLending.json @@ -0,0 +1,604 @@ +{ + "address": "0xBB505c54D71E9e599cB8435b4F0cEEc05fC71cbD", + "abi": [ + { + "inputs": [ + { "internalType": "address", "name": "_addressRegistry", "type": "address" }, + { "internalType": "address", "name": "_WETH9", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "contractAddress", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "Borrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": false, "internalType": "address", "name": "user", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "reserveAmount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }, + { "indexed": true, "internalType": "uint16", "name": "referral", "type": "uint16" } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "vaultId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "vaultAddress", "type": "address" } + ], + "name": "DisableVaultToBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "vaultId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "vaultAddress", "type": "address" } + ], + "name": "EnableVaultToBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "reserve", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "eTokenAddress", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "stakingAddress", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" } + ], + "name": "InitReserve", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "Paused", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "user", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "underlyingTokenAmount", "type": "uint256" } + ], + "name": "Redeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "contractAddress", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "Repay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveBorrowDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveBorrowEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveDeActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveFrozen", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "ReserveUnFreeze", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "vaultId", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "vaultAddress", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "credit", "type": "uint256" } + ], + "name": "SetCreditsOfVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": false, "internalType": "uint16", "name": "utilizationA", "type": "uint16" }, + { "indexed": false, "internalType": "uint16", "name": "borrowingRateA", "type": "uint16" }, + { "indexed": false, "internalType": "uint16", "name": "utilizationB", "type": "uint16" }, + { "indexed": false, "internalType": "uint16", "name": "borrowingRateB", "type": "uint16" }, + { "indexed": false, "internalType": "uint16", "name": "maxBorrowingRate", "type": "uint16" } + ], + "name": "SetInterestRateConfig", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "cap", "type": "uint256" } + ], + "name": "SetReserveCapacity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "feeRate", "type": "uint256" } + ], + "name": "SetReserveFeeRate", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "UnPaused", "type": "event" }, + { + "inputs": [], + "name": "WETH9", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "activateReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addressRegistry", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint256", "name": "debtId", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "borrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "borrowingRateOfReserve", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "borrowingWhiteList", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "credits", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "deActivateReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "debtPositions", + "outputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint256", "name": "borrowed", "type": "uint256" }, + { "internalType": "uint256", "name": "borrowedIndex", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "deposit", + "outputs": [{ "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "depositAndStake", + "outputs": [{ "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "disableBorrowing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "vaultId", "type": "uint256" }], + "name": "disableVaultToBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "emergencyPauseAll", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "enableBorrowing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "vaultId", "type": "uint256" }], + "name": "enableVaultToBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "exchangeRateOfReserve", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "freezeReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "debtId", "type": "uint256" }], + "name": "getCurrentDebt", + "outputs": [ + { "internalType": "uint256", "name": "currentDebt", "type": "uint256" }, + { "internalType": "uint256", "name": "latestBorrowingIndex", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "getETokenAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256[]", "name": "reserveIdArr", "type": "uint256[]" }, + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getPositionStatus", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "address", "name": "user", "type": "address" }, + { "internalType": "uint256", "name": "eTokenStaked", "type": "uint256" }, + { "internalType": "uint256", "name": "eTokenUnStaked", "type": "uint256" }, + { "internalType": "uint256", "name": "liquidity", "type": "uint256" } + ], + "internalType": "struct ILendingPool.PositionStatus[]", + "name": "statusArr", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "debtId", "type": "uint256" }], + "name": "getReserveIdOfDebt", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256[]", "name": "reserveIdArr", "type": "uint256[]" }], + "name": "getReserveStatus", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "address", "name": "underlyingTokenAddress", "type": "address" }, + { "internalType": "address", "name": "eTokenAddress", "type": "address" }, + { "internalType": "address", "name": "stakingAddress", "type": "address" }, + { "internalType": "uint256", "name": "totalLiquidity", "type": "uint256" }, + { "internalType": "uint256", "name": "totalBorrows", "type": "uint256" }, + { "internalType": "uint256", "name": "exchangeRate", "type": "uint256" }, + { "internalType": "uint256", "name": "borrowingRate", "type": "uint256" } + ], + "internalType": "struct ILendingPool.ReserveStatus[]", + "name": "statusArr", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "getStakingAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "getUnderlyingTokenAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "asset", "type": "address" }], + "name": "initReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "newDebtPosition", + "outputs": [{ "internalType": "uint256", "name": "debtId", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nextDebtPositionId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextReserveId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "bool", "name": "receiveNativeETH", "type": "bool" } + ], + "name": "redeem", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint256", "name": "debtId", "type": "uint256" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "repay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "reserves", + "outputs": [ + { "internalType": "uint256", "name": "borrowingIndex", "type": "uint256" }, + { "internalType": "uint256", "name": "currentBorrowingRate", "type": "uint256" }, + { "internalType": "uint256", "name": "totalBorrows", "type": "uint256" }, + { "internalType": "address", "name": "underlyingTokenAddress", "type": "address" }, + { "internalType": "address", "name": "eTokenAddress", "type": "address" }, + { "internalType": "address", "name": "stakingAddress", "type": "address" }, + { "internalType": "uint256", "name": "reserveCapacity", "type": "uint256" }, + { + "components": [ + { "internalType": "uint128", "name": "utilizationA", "type": "uint128" }, + { "internalType": "uint128", "name": "borrowingRateA", "type": "uint128" }, + { "internalType": "uint128", "name": "utilizationB", "type": "uint128" }, + { "internalType": "uint128", "name": "borrowingRateB", "type": "uint128" }, + { "internalType": "uint128", "name": "maxBorrowingRate", "type": "uint128" } + ], + "internalType": "struct DataTypes.InterestRateConfig", + "name": "borrowingRateConfig", + "type": "tuple" + }, + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "uint128", "name": "lastUpdateTimestamp", "type": "uint128" }, + { "internalType": "uint16", "name": "reserveFeeRate", "type": "uint16" }, + { + "components": [ + { "internalType": "bool", "name": "isActive", "type": "bool" }, + { "internalType": "bool", "name": "frozen", "type": "bool" }, + { "internalType": "bool", "name": "borrowingEnabled", "type": "bool" } + ], + "internalType": "struct DataTypes.Flags", + "name": "flags", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint16", "name": "utilizationA", "type": "uint16" }, + { "internalType": "uint16", "name": "borrowingRateA", "type": "uint16" }, + { "internalType": "uint16", "name": "utilizationB", "type": "uint16" }, + { "internalType": "uint16", "name": "borrowingRateB", "type": "uint16" }, + { "internalType": "uint16", "name": "maxBorrowingRate", "type": "uint16" } + ], + "name": "setBorrowingRateConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "vaultId", "type": "uint256" }, + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "credit", "type": "uint256" } + ], + "name": "setCreditsOfVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "cap", "type": "uint256" } + ], + "name": "setReserveCapacity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint16", "name": "_rate", "type": "uint16" } + ], + "name": "setReserveFeeRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "totalBorrowsOfReserve", + "outputs": [{ "internalType": "uint256", "name": "totalBorrows", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "totalLiquidityOfReserve", + "outputs": [{ "internalType": "uint256", "name": "totalLiquidity", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "unFreezeReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "unPauseAll", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { "internalType": "uint256", "name": "reserveId", "type": "uint256" }, + { "internalType": "uint256", "name": "eTokenAmount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "bool", "name": "receiveNativeETH", "type": "bool" } + ], + "name": "unStakeAndWithdraw", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "reserveId", "type": "uint256" }], + "name": "utilizationRateOfReserve", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } + ] +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 640f4ffe5..ffd24c359 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -223,6 +223,7 @@ export default { }, periphery: { optimism: { + extraReserve: 50, uniswapFees: [ { assets: ["WETH", "OP"], fee: 0.3 }, { assets: ["USDC", "OP"], fee: 0.3 }, @@ -364,6 +365,7 @@ declare module "hardhat/types/config" { } export interface PeripheryConfig { + extraReserve?: number; uniswapFees: { assets: [string, string]; fee: number }[]; } diff --git a/test/VotePreviewer.t.sol b/test/VotePreviewer.t.sol new file mode 100644 index 000000000..51c209ff8 --- /dev/null +++ b/test/VotePreviewer.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.17; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol"; +import { VotePreviewer, IERC20, IPool, IExtraLending } from "../contracts/periphery/VotePreviewer.sol"; +import { ForkTest } from "./Fork.t.sol"; + +contract VotePreviewerTest is ForkTest { + using FixedPointMathLib for uint256; + + address internal exa; + IPool internal pool; + IERC20 internal gauge; + IExtraLending internal extraLending; + VotePreviewer internal previewer; + + function setUp() external { + vm.createSelectFork(vm.envString("OPTIMISM_NODE"), 110_504_307); + + exa = deployment("EXA"); + pool = IPool(deployment("EXAPool")); + gauge = IERC20(deployment("EXAGauge")); + extraLending = IExtraLending(deployment("ExtraLending")); + previewer = VotePreviewer( + address(new ERC1967Proxy(address(new VotePreviewer(exa, pool, gauge, extraLending, 50)), "")) + ); + } + + function testExternalVotes() external { + assertEq(previewer.externalVotes(0x23fD464e0b0eE21cEdEb929B19CABF9bD5215019), 27401932247383718289362); + } +}