From 8d2b348ccec9917a4832adc24be4193987d9744f Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 5 Sep 2023 13:52:23 -0400 Subject: [PATCH 01/42] package-lock update --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d8a1f85..b8f50b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10817,7 +10817,7 @@ "fs-readdir-recursive": "^1.1.0", "lodash": "^4.17.14", "markdown-table": "^1.1.3", - "mocha": "10.2.0", + "mocha": "^7.1.1", "req-cwd": "^2.0.0", "request": "^2.88.0", "request-promise-native": "^1.0.5", @@ -11512,7 +11512,7 @@ "keccak": "^3.0.2", "lodash": "^4.17.11", "mnemonist": "^0.38.0", - "mocha": "10.2.0", + "mocha": "^10.0.0", "p-map": "^4.0.0", "qs": "^6.7.0", "raw-body": "^2.4.1", @@ -13646,7 +13646,7 @@ "globby": "^10.0.1", "jsonschema": "^1.2.4", "lodash": "^4.17.15", - "mocha": "10.2.0", + "mocha": "7.1.2", "node-emoji": "^1.10.0", "pify": "^4.0.1", "recursive-readdir": "^2.2.2", From 272a65b071c42bc2e75fe63cb45956824991d429 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sun, 17 Mar 2024 23:49:18 -0400 Subject: [PATCH 02/42] added in myso oracle contracts --- .../interfaces/oracles/IWSTETH.sol | 13 ++ .../oracles/custom/MysoOracle.sol | 121 ++++++++++++++++++ package-lock.json | 6 +- 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IWSTETH.sol create mode 100644 contracts/peer-to-peer/oracles/custom/MysoOracle.sol diff --git a/contracts/peer-to-peer/interfaces/oracles/IWSTETH.sol b/contracts/peer-to-peer/interfaces/oracles/IWSTETH.sol new file mode 100644 index 00000000..2213234a --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IWSTETH.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IWSTETH { + /** + * @notice gets amount of stEth for given wstEth + * @param _wstETHAmount amount of wstEth + * @return amount of stEth + */ + function getStETHByWstETH( + uint256 _wstETHAmount + ) external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol new file mode 100644 index 00000000..0aad4690 --- /dev/null +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; +import {Errors} from "../../../Errors.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +contract MysoOracle is ChainlinkBase, Ownable2Step { + struct MysoPrice { + uint112 priceUntilTimestampPassed; + uint112 priceOnceTimestampPassed; + uint32 timestampLatestProposedPriceBecomesValid; + } + + // solhint-disable var-name-mixedcase + address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address + address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // weth + address internal constant WSTETH = + 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //wsteth + address internal constant STETH = + 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; //steth + uint256 internal constant MYSO_IOO_BASE_CURRENCY_UNIT = 1e18; // 18 decimals for ETH based oracles + address internal constant ETH_USD_CHAINLINK = + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; //eth usd chainlink + + uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 days; + + MysoPrice public mysoPrice; + + /** + * @dev constructor for MysoOracle + * @param _tokenAddrs array of token addresses + * @param _oracleAddrs array of oracle addresses + * @param _mysoUsdPrice initial price of myso in usd (use 8 decimals like chainlink) (eg. 0.50 USD = 0.5 * 1e8) + */ + constructor( + address[] memory _tokenAddrs, + address[] memory _oracleAddrs, + uint112 _mysoUsdPrice + ) ChainlinkBase(_tokenAddrs, _oracleAddrs, MYSO_IOO_BASE_CURRENCY_UNIT) { + mysoPrice = MysoPrice( + _mysoUsdPrice, + _mysoUsdPrice, + uint32(block.timestamp) + ); + } + + /** + * @dev updates timestampLatestProposedPriceBecomesValid and priceOnceTimestampPassed + * only updates priceUntilTimestampPassed if the prior time lock had passed + * @param _newMysoUsdPrice initial price of myso in usd (use 8 decimals like chainlink) (eg. 0.50 USD = 0.5 * 1e8) + */ + + function setMysoPrice(uint112 _newMysoUsdPrice) external onlyOwner { + if ( + block.timestamp < mysoPrice.timestampLatestProposedPriceBecomesValid + ) { + // if the priceOnceTimestampPassed is not yet active, update that price, + // leave priceUntilTimestampPassed the same but reset the time lock + mysoPrice = MysoPrice( + mysoPrice.priceUntilTimestampPassed, + _newMysoUsdPrice, + uint32(block.timestamp + MYSO_PRICE_TIME_LOCK) + ); + } else { + // if the priceOnceTimestampPassed is not yet active, update the priceUntilTimestampPassed with old priceOnceTimestampPassed, + // update the priceOnceTimestampPassed with new price, and reset the time lock + mysoPrice = MysoPrice( + mysoPrice.priceOnceTimestampPassed, + _newMysoUsdPrice, + uint32(block.timestamp + MYSO_PRICE_TIME_LOCK) + ); + } + } + + function _getPriceOfToken( + address token + ) internal view virtual override returns (uint256 tokenPriceRaw) { + if (token == MYSO) { + tokenPriceRaw = _getMysoPriceInEth(); + } else if (token == WETH) { + tokenPriceRaw = 1e18; + } else if (token == WSTETH) { + tokenPriceRaw = _getWstEthPrice(); + } else { + tokenPriceRaw = super._getPriceOfToken(token); + } + } + + function _getWstEthPrice() internal view returns (uint256 wstEthPriceRaw) { + uint256 stEthAmountPerWstEth = IWSTETH(WSTETH).getStETHByWstETH(1e18); + uint256 stEthPriceInEth = _getPriceOfToken(STETH); + wstEthPriceRaw = Math.mulDiv( + stEthPriceInEth, + stEthAmountPerWstEth, + 1e18 + ); + } + + function _getMysoPriceInEth() + internal + view + returns (uint256 mysoPriceInEth) + { + uint256 mysoPriceInUsd = block.timestamp < + mysoPrice.timestampLatestProposedPriceBecomesValid + ? mysoPrice.priceUntilTimestampPassed + : mysoPrice.priceOnceTimestampPassed; + uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( + ETH_USD_CHAINLINK + ); + mysoPriceInEth = Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); + } +} diff --git a/package-lock.json b/package-lock.json index b8f50b7a..6d8a1f85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10817,7 +10817,7 @@ "fs-readdir-recursive": "^1.1.0", "lodash": "^4.17.14", "markdown-table": "^1.1.3", - "mocha": "^7.1.1", + "mocha": "10.2.0", "req-cwd": "^2.0.0", "request": "^2.88.0", "request-promise-native": "^1.0.5", @@ -11512,7 +11512,7 @@ "keccak": "^3.0.2", "lodash": "^4.17.11", "mnemonist": "^0.38.0", - "mocha": "^10.0.0", + "mocha": "10.2.0", "p-map": "^4.0.0", "qs": "^6.7.0", "raw-body": "^2.4.1", @@ -13646,7 +13646,7 @@ "globby": "^10.0.1", "jsonschema": "^1.2.4", "lodash": "^4.17.15", - "mocha": "7.1.2", + "mocha": "10.2.0", "node-emoji": "^1.10.0", "pify": "^4.0.1", "recursive-readdir": "^2.2.2", From 4ab992e4c8886574316724b1f88b375e9e8fff25 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Mon, 18 Mar 2024 15:48:51 -0400 Subject: [PATCH 03/42] added updates to myso oracle --- .../oracles/custom/MysoOracle.sol | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 0aad4690..1621f79f 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -14,9 +14,9 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; */ contract MysoOracle is ChainlinkBase, Ownable2Step { struct MysoPrice { - uint112 priceUntilTimestampPassed; - uint112 priceOnceTimestampPassed; - uint32 timestampLatestProposedPriceBecomesValid; + uint112 prePrice; + uint112 postPrice; + uint32 switchTime; } // solhint-disable var-name-mixedcase @@ -30,10 +30,16 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { address internal constant ETH_USD_CHAINLINK = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; //eth usd chainlink - uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 days; + uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 hours; MysoPrice public mysoPrice; + event MysoPriceUpdated( + uint112 prePrice, + uint112 postPrice, + uint32 switchTime + ); + /** * @dev constructor for MysoOracle * @param _tokenAddrs array of token addresses @@ -50,33 +56,44 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { _mysoUsdPrice, uint32(block.timestamp) ); + _transferOwnership(msg.sender); } /** - * @dev updates timestampLatestProposedPriceBecomesValid and priceOnceTimestampPassed - * only updates priceUntilTimestampPassed if the prior time lock had passed + * @dev updates postPrice and switchTime + * only updates prePrice if the switchTime has passed * @param _newMysoUsdPrice initial price of myso in usd (use 8 decimals like chainlink) (eg. 0.50 USD = 0.5 * 1e8) */ function setMysoPrice(uint112 _newMysoUsdPrice) external onlyOwner { - if ( - block.timestamp < mysoPrice.timestampLatestProposedPriceBecomesValid - ) { - // if the priceOnceTimestampPassed is not yet active, update that price, - // leave priceUntilTimestampPassed the same but reset the time lock + MysoPrice memory currMysoPrice = mysoPrice; + uint32 newTimeStamp = uint32(block.timestamp + MYSO_PRICE_TIME_LOCK); + if (block.timestamp < currMysoPrice.switchTime) { + // if the switchTime has not yet passed, update only postPrice with new price, + // leave prePrice the same and update switchTime mysoPrice = MysoPrice( - mysoPrice.priceUntilTimestampPassed, + currMysoPrice.prePrice, _newMysoUsdPrice, - uint32(block.timestamp + MYSO_PRICE_TIME_LOCK) + newTimeStamp + ); + emit MysoPriceUpdated( + currMysoPrice.prePrice, + _newMysoUsdPrice, + newTimeStamp ); } else { - // if the priceOnceTimestampPassed is not yet active, update the priceUntilTimestampPassed with old priceOnceTimestampPassed, - // update the priceOnceTimestampPassed with new price, and reset the time lock + // if the switchTime has passed (or exactly equal), update the prePrice with postPrice, + // update the postPrice with new price, and update switchTime mysoPrice = MysoPrice( - mysoPrice.priceOnceTimestampPassed, + mysoPrice.postPrice, _newMysoUsdPrice, uint32(block.timestamp + MYSO_PRICE_TIME_LOCK) ); + emit MysoPriceUpdated( + currMysoPrice.postPrice, + _newMysoUsdPrice, + newTimeStamp + ); } } @@ -109,10 +126,9 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { view returns (uint256 mysoPriceInEth) { - uint256 mysoPriceInUsd = block.timestamp < - mysoPrice.timestampLatestProposedPriceBecomesValid - ? mysoPrice.priceUntilTimestampPassed - : mysoPrice.priceOnceTimestampPassed; + uint256 mysoPriceInUsd = block.timestamp < mysoPrice.switchTime + ? mysoPrice.prePrice + : mysoPrice.postPrice; uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( ETH_USD_CHAINLINK ); From 94642b58259e1880a6b79bea3caeb256dc475ccd Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Mon, 18 Mar 2024 17:51:37 -0400 Subject: [PATCH 04/42] add tests, finish contract, and update readme --- README.md | 2 +- .../oracles/custom/MysoOracle.sol | 47 +++- hardhat.config.ts | 11 + .../mainnet-myso-oracle-forked-tests.ts | 261 ++++++++++++++++++ 4 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts diff --git a/README.md b/README.md index b5934c27..57618d01 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The protocol supports two different models, each targeted at different use cases ## Quick Start ``` npm i -npx hardhat test +npx hardhat test --grep "IOO price correctly" ``` ## Contract Files diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 1621f79f..1869ff39 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -3,11 +3,11 @@ pragma solidity 0.8.19; import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; -import {Errors} from "../../../Errors.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces @@ -24,11 +24,11 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // weth address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //wsteth - address internal constant STETH = - 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; //steth uint256 internal constant MYSO_IOO_BASE_CURRENCY_UNIT = 1e18; // 18 decimals for ETH based oracles address internal constant ETH_USD_CHAINLINK = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; //eth usd chainlink + address internal constant STETH_ETH_CHAINLINK = + 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; //steth eth chainlink uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 hours; @@ -40,6 +40,8 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { uint32 switchTime ); + error NoMyso(); + /** * @dev constructor for MysoOracle * @param _tokenAddrs array of token addresses @@ -97,6 +99,41 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { } } + function getPrice( + address collToken, + address loanToken + ) external view override returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = (loanToken == MYSO) + ? 18 + : IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = + (priceOfCollToken * 10 ** loanTokenDecimals) / + priceOfLoanToken; + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + override + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + // must have at least one token is MYSO to use this oracle + if (collToken != MYSO && loanToken != MYSO) { + revert NoMyso(); + } + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + function _getPriceOfToken( address token ) internal view virtual override returns (uint256 tokenPriceRaw) { @@ -113,7 +150,9 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { function _getWstEthPrice() internal view returns (uint256 wstEthPriceRaw) { uint256 stEthAmountPerWstEth = IWSTETH(WSTETH).getStETHByWstETH(1e18); - uint256 stEthPriceInEth = _getPriceOfToken(STETH); + uint256 stEthPriceInEth = _checkAndReturnLatestRoundData( + (STETH_ETH_CHAINLINK) + ); wstEthPriceRaw = Math.mulDiv( stEthPriceInEth, stEthAmountPerWstEth, diff --git a/hardhat.config.ts b/hardhat.config.ts index d44dea70..82dc26f1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -28,6 +28,17 @@ export const getRecentMainnetForkingConfig = () => { return { chainId: chainId, url: url, blockNumber: blockNumber } } +export const getMysoOracleMainnetForkingConfig = () => { + const INFURA_API_KEY = process.env.INFURA_API_KEY + if (INFURA_API_KEY === undefined) { + throw new Error('Invalid hardhat.config.ts! Need to set `INFURA_API_KEY`!') + } + const chainId = 1 + const url = `https://mainnet.infura.io/v3/${INFURA_API_KEY}` + const blockNumber = 19300000 // 2024-02-24 (9PM UTC) + return { chainId: chainId, url: url, blockNumber: blockNumber } +} + export const getArbitrumForkingConfig = () => { const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY if (ALCHEMY_API_KEY === undefined) { diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts new file mode 100644 index 00000000..40909f6f --- /dev/null +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -0,0 +1,261 @@ +import { expect } from 'chai' +import { ethers } from 'hardhat' +import { HARDHAT_CHAIN_ID_AND_FORKING_CONFIG, getMysoOracleMainnetForkingConfig } from '../../hardhat.config' + +// test config constants & vars +let snapshotId: String // use snapshot id to reset state before each test + +// constants +const hre = require('hardhat') +const BASE = ethers.BigNumber.from(10).pow(18) +const ONE_USDC = ethers.BigNumber.from(10).pow(6) +const ONE_WETH = ethers.BigNumber.from(10).pow(18) +const ONE_MYSO = ethers.BigNumber.from(10).pow(18) +const ONE_WSTETH = ethers.BigNumber.from(10).pow(18) +const MAX_UINT128 = ethers.BigNumber.from(2).pow(128).sub(1) +const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) +const ONE_HOUR = ethers.BigNumber.from(60 * 60) +const ZERO_ADDR = '0x0000000000000000000000000000000000000000' +const ZERO_BYTES32 = ethers.utils.formatBytes32String('') + +describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { + before(async () => { + console.log('Note: Running mainnet tests with the following forking config:') + console.log(HARDHAT_CHAIN_ID_AND_FORKING_CONFIG) + if (HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId !== 1) { + console.warn('Invalid hardhat forking config! Expected `HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId` to be 1!') + + console.warn('Assuming that current test run is using `npx hardhat coverage`!') + + console.warn('Re-importing mainnet forking config from `hardhat.config.ts`...') + const mainnetForkingConfig = getMysoOracleMainnetForkingConfig() + + console.warn('Overwriting chainId to hardhat default `31337` to make off-chain signing consistent...') + HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId = 31337 + + console.log('block number: ', mainnetForkingConfig.url) + + console.warn('Trying to manually switch network to forked mainnet for this test file...') + await hre.network.provider.request({ + method: 'hardhat_reset', + params: [ + { + forking: { + jsonRpcUrl: mainnetForkingConfig.url, + blockNumber: mainnetForkingConfig.blockNumber + } + } + ] + }) + } + }) + + beforeEach(async () => { + snapshotId = await hre.network.provider.send('evm_snapshot') + }) + + afterEach(async () => { + await hre.network.provider.send('evm_revert', [snapshotId]) + }) + + async function setupTest() { + const [lender, signer, borrower, team, whitelistAuthority, someUser] = await ethers.getSigners() + /* ************************************ */ + /* DEPLOYMENT OF SYSTEM CONTRACTS START */ + /* ************************************ */ + // deploy address registry + const AddressRegistry = await ethers.getContractFactory('AddressRegistry') + const addressRegistry = await AddressRegistry.connect(team).deploy() + await addressRegistry.deployed() + + // deploy borrower gate way + const BorrowerGateway = await ethers.getContractFactory('BorrowerGateway') + const borrowerGateway = await BorrowerGateway.connect(team).deploy(addressRegistry.address) + await borrowerGateway.deployed() + + // deploy quote handler + const QuoteHandler = await ethers.getContractFactory('QuoteHandler') + const quoteHandler = await QuoteHandler.connect(team).deploy(addressRegistry.address) + await quoteHandler.deployed() + + // deploy lender vault implementation + const LenderVaultImplementation = await ethers.getContractFactory('LenderVaultImpl') + const lenderVaultImplementation = await LenderVaultImplementation.connect(team).deploy() + await lenderVaultImplementation.deployed() + + // deploy LenderVaultFactory + const LenderVaultFactory = await ethers.getContractFactory('LenderVaultFactory') + const lenderVaultFactory = await LenderVaultFactory.connect(team).deploy( + addressRegistry.address, + lenderVaultImplementation.address + ) + await lenderVaultFactory.deployed() + + // initialize address registry + await addressRegistry.connect(team).initialize(lenderVaultFactory.address, borrowerGateway.address, quoteHandler.address) + + /* ********************************** */ + /* DEPLOYMENT OF SYSTEM CONTRACTS END */ + /* ********************************** */ + + // create a vault + await lenderVaultFactory.connect(lender).createVault(ZERO_BYTES32) + const lenderVaultAddrs = await addressRegistry.registeredVaults() + const lenderVaultAddr = lenderVaultAddrs[0] + const lenderVault = await LenderVaultImplementation.attach(lenderVaultAddr) + + // prepare WETH balance + const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' + const weth = await ethers.getContractAt('IWETH', WETH_ADDRESS) + await ethers.provider.send('hardhat_setBalance', [borrower.address, '0x204FCE5E3E25026110000000']) + await weth.connect(borrower).deposit({ value: ONE_WETH.mul(1) }) + + //prepare wstEth balances + const WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' + const WSTETH_HOLDER = '0x5fEC2f34D80ED82370F733043B6A536d7e9D7f8d' + const wsteth = await ethers.getContractAt('IWETH', WSTETH_ADDRESS) + await ethers.provider.send('hardhat_setBalance', [WSTETH_HOLDER, '0x56BC75E2D63100000']) + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [WSTETH_HOLDER] + }) + + const wstEthHolder = await ethers.getSigner(WSTETH_HOLDER) + + await wsteth.connect(wstEthHolder).transfer(team.address, '10000000000000000000') + + const reth = '0xae78736Cd615f374D3085123A210448E74Fc6393' + const cbeth = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704' + const rethToEthChainlinkAddr = '0x536218f9E9Eb48863970252233c8F271f554C2d0' + const cbethToEthChainlinkAddr = '0xF017fcB346A1885194689bA23Eff2fE6fA5C483b' + + return { + addressRegistry, + borrowerGateway, + quoteHandler, + lenderVaultImplementation, + lender, + signer, + borrower, + team, + whitelistAuthority, + weth, + wsteth, + reth, + cbeth, + rethToEthChainlinkAddr, + cbethToEthChainlinkAddr, + lenderVault, + lenderVaultFactory, + someUser + } + } + + describe('Myso Oracle Testing', function () { + it('Should set up myso IOO price correctly', async function () { + const { + addressRegistry, + borrowerGateway, + quoteHandler, + lender, + borrower, + team, + weth, + wsteth, + reth, + cbeth, + cbethToEthChainlinkAddr, + rethToEthChainlinkAddr, + lenderVault + } = await setupTest() + + const myso = '0x00000000000000000000000000000000DeaDBeef' + + // deploy myso oracle + const MysoOracle = await ethers.getContractFactory('MysoOracle') + + const mysoOracle = await MysoOracle.connect(team).deploy( + [reth, cbeth], + [rethToEthChainlinkAddr, cbethToEthChainlinkAddr], + 50000000 + ) + await mysoOracle.deployed() + + const mysoPriceData = await mysoOracle.mysoPrice() + + expect(mysoPriceData.prePrice).to.equal(50000000) + expect(mysoPriceData.postPrice).to.equal(50000000) + const timestampAtDeployment = mysoPriceData.switchTime + + await expect(mysoOracle.connect(lender).setMysoPrice(80000000)).to.be.revertedWith('Ownable: caller is not the owner') + + await expect(mysoOracle.getPrice(weth.address, cbeth)).to.be.revertedWithCustomError(mysoOracle, 'NoMyso') + + const wethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) + const wstEthCollMysoLoanPrice = await mysoOracle.getPrice(wsteth.address, myso) + const rethCollMysoLoanPrice = await mysoOracle.getPrice(reth, myso) + const cbethCollMysoLoanPrice = await mysoOracle.getPrice(cbeth, myso) + + //toggle to show logs + const showLogs = true + if (showLogs) { + console.log( + 'wethCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(wethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'wstEthCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(wstEthCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'rethCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(rethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'cbEthCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(cbethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + } + + await mysoOracle.connect(team).setMysoPrice(100000000) + const newMysoPriceData = await mysoOracle.mysoPrice() + expect(newMysoPriceData.prePrice).to.equal(50000000) + expect(newMysoPriceData.postPrice).to.equal(100000000) + expect(newMysoPriceData.switchTime).to.be.gte(ethers.BigNumber.from(timestampAtDeployment).add(ONE_HOUR)) + const newWethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) + expect(newWethCollMysoLoanPrice).to.equal(wethCollMysoLoanPrice) + await ethers.provider.send('evm_mine', [ethers.BigNumber.from(newMysoPriceData.switchTime).add(10).toNumber()]) + const wethCollMysoLoanPostPrice = await mysoOracle.getPrice(weth.address, myso) + // difference is very small less than the order of 10^-13 + expect( + wethCollMysoLoanPostPrice + .sub(wethCollMysoLoanPrice.div(2)) + .mul(ethers.BigNumber.from(10).pow(13)) + .div(wethCollMysoLoanPostPrice) + ).to.be.equal(0) + + const wstEthCollMysoLoanPostPrice = await mysoOracle.getPrice(wsteth.address, myso) + const rethCollMysoLoanPostPrice = await mysoOracle.getPrice(reth, myso) + const cbethCollMysoLoanPostPrice = await mysoOracle.getPrice(cbeth, myso) + + if (showLogs) { + console.log( + 'wethCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(wethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'wstEthCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(wstEthCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'rethCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(rethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'cbEthCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(cbethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + } + }) + }) +}) From 9f713019c0592e97b9d14b1ce77f79307361a473 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Wed, 20 Mar 2024 14:28:52 -0400 Subject: [PATCH 05/42] added meth, rpl, and usdc plus test --- .../peer-to-peer/interfaces/oracles/IMETH.sol | 11 ++++ .../oracles/custom/MysoOracle.sol | 21 ++++++++ .../mainnet-myso-oracle-forked-tests.ts | 52 ++++++++++++++++++- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IMETH.sol diff --git a/contracts/peer-to-peer/interfaces/oracles/IMETH.sol b/contracts/peer-to-peer/interfaces/oracles/IMETH.sol new file mode 100644 index 00000000..e3043cea --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IMETH.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IMETH { + /** + * @notice gets amount of Eth for given mEth + * @param mETHAmount amount of mEth + * @return amount of stEth + */ + function mETHToETH(uint256 mETHAmount) external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 1869ff39..e6522a8a 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -6,6 +6,7 @@ import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; +import {IMETH} from "../../interfaces/oracles/IMETH.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -24,11 +25,17 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // weth address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //wsteth + address internal constant METH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; //meth + address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; //rpl + address internal constant METH_STAKING_CONTRACT = + 0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f; //meth staking contract uint256 internal constant MYSO_IOO_BASE_CURRENCY_UNIT = 1e18; // 18 decimals for ETH based oracles address internal constant ETH_USD_CHAINLINK = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; //eth usd chainlink address internal constant STETH_ETH_CHAINLINK = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; //steth eth chainlink + address internal constant RPL_USD_CHAINLINK = + 0x4E155eD98aFE9034b7A5962f6C84c86d869daA9d; //rpl usd chainlink uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 hours; @@ -143,6 +150,10 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { tokenPriceRaw = 1e18; } else if (token == WSTETH) { tokenPriceRaw = _getWstEthPrice(); + } else if (token == METH) { + tokenPriceRaw = IMETH(METH_STAKING_CONTRACT).mETHToETH(1e18); + } else if (token == RPL) { + tokenPriceRaw = _getRPLPriceInEth(); } else { tokenPriceRaw = super._getPriceOfToken(token); } @@ -173,4 +184,14 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { ); mysoPriceInEth = Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); } + + function _getRPLPriceInEth() internal view returns (uint256 rplPriceRaw) { + uint256 rplPriceInUSD = _checkAndReturnLatestRoundData( + (RPL_USD_CHAINLINK) + ); + uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( + ETH_USD_CHAINLINK + ); + rplPriceRaw = Math.mulDiv(rplPriceInUSD, 1e18, ethPriceInUsd); + } } diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index 40909f6f..b2d63573 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -170,13 +170,17 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { } = await setupTest() const myso = '0x00000000000000000000000000000000DeaDBeef' + const meth = '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa' + const rpl = '0xD33526068D116cE69F19A9ee46F0bd304F21A51f' + const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' + const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' // deploy myso oracle const MysoOracle = await ethers.getContractFactory('MysoOracle') const mysoOracle = await MysoOracle.connect(team).deploy( - [reth, cbeth], - [rethToEthChainlinkAddr, cbethToEthChainlinkAddr], + [reth, cbeth, usdc], + [rethToEthChainlinkAddr, cbethToEthChainlinkAddr, usdcToEthChainlinkAddr], 50000000 ) await mysoOracle.deployed() @@ -195,6 +199,9 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const wstEthCollMysoLoanPrice = await mysoOracle.getPrice(wsteth.address, myso) const rethCollMysoLoanPrice = await mysoOracle.getPrice(reth, myso) const cbethCollMysoLoanPrice = await mysoOracle.getPrice(cbeth, myso) + const usdcCollMysoLoanPrice = await mysoOracle.getPrice(usdc, myso) + const rplCollMysoLoanPrice = await mysoOracle.getPrice(rpl, myso) + const methCollMysoLoanPrice = await mysoOracle.getPrice(meth, myso) //toggle to show logs const showLogs = true @@ -215,6 +222,25 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { 'cbEthCollMysoLoanPrice', Math.round(1000000 * Number(ethers.utils.formatUnits(cbethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 ) + console.log( + 'rplCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(rplCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'methCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(methCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'usdcCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log(ethers.utils.formatUnits(wethCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(wstEthCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(rethCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(cbethCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(rplCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(methCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) } await mysoOracle.connect(team).setMysoPrice(100000000) @@ -237,6 +263,9 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const wstEthCollMysoLoanPostPrice = await mysoOracle.getPrice(wsteth.address, myso) const rethCollMysoLoanPostPrice = await mysoOracle.getPrice(reth, myso) const cbethCollMysoLoanPostPrice = await mysoOracle.getPrice(cbeth, myso) + const rplCollMysoLoanPostPrice = await mysoOracle.getPrice(rpl, myso) + const methCollMysoLoanPostPrice = await mysoOracle.getPrice(meth, myso) + const usdcCollMysoLoanPostPrice = await mysoOracle.getPrice(usdc, myso) if (showLogs) { console.log( @@ -255,6 +284,25 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { 'cbEthCollMysoLoanPostPrice', Math.round(1000000 * Number(ethers.utils.formatUnits(cbethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 ) + console.log( + 'rplCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(rplCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'methCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(methCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'usdcCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) + console.log(ethers.utils.formatUnits(wethCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(wstEthCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(rethCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(cbethCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(rplCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(methCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18)) } }) }) From 56e4457dcff6fd5ea8df068574a63e7e2c7761e6 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Thu, 21 Mar 2024 09:56:51 -0400 Subject: [PATCH 06/42] added usdt and dai to test --- .../mainnet-myso-oracle-forked-tests.ts | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index b2d63573..ee0b833c 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -173,14 +173,24 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const meth = '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa' const rpl = '0xD33526068D116cE69F19A9ee46F0bd304F21A51f' const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' + const dai = '0x6B175474E89094C44Da98b954EedeAC495271d0F' + const usdt = '0xdAC17F958D2ee523a2206206994597C13D831ec7' const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' + const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' + const usdtToEthChainlinkAddr = '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46' // deploy myso oracle const MysoOracle = await ethers.getContractFactory('MysoOracle') const mysoOracle = await MysoOracle.connect(team).deploy( - [reth, cbeth, usdc], - [rethToEthChainlinkAddr, cbethToEthChainlinkAddr, usdcToEthChainlinkAddr], + [reth, cbeth, usdc, dai, usdt], + [ + rethToEthChainlinkAddr, + cbethToEthChainlinkAddr, + usdcToEthChainlinkAddr, + daiToEthChainlinkAddr, + usdtToEthChainlinkAddr + ], 50000000 ) await mysoOracle.deployed() @@ -200,9 +210,17 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const rethCollMysoLoanPrice = await mysoOracle.getPrice(reth, myso) const cbethCollMysoLoanPrice = await mysoOracle.getPrice(cbeth, myso) const usdcCollMysoLoanPrice = await mysoOracle.getPrice(usdc, myso) + const usdtCollMysoLoanPrice = await mysoOracle.getPrice(usdt, myso) + const daiCollMysoLoanPrice = await mysoOracle.getPrice(dai, myso) const rplCollMysoLoanPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPrice = await mysoOracle.getPrice(meth, myso) + const mysoCollWethLoanPrice = await mysoOracle.getPrice(myso, weth.address) + const mysoCollWstEthLoanPrice = await mysoOracle.getPrice(myso, wsteth.address) + const mysoCollUsdcLoanPrice = await mysoOracle.getPrice(myso, usdc) + const mysoCollUsdtLoanPrice = await mysoOracle.getPrice(myso, usdt) + const mysoCollDaiLoanPrice = await mysoOracle.getPrice(myso, dai) + //toggle to show logs const showLogs = true if (showLogs) { @@ -241,6 +259,13 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(rplCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(methCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(usdtCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(daiCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollWethLoanPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) + console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPrice, 6)) + console.log(ethers.utils.formatUnits(mysoCollDaiLoanPrice, 18)) } await mysoOracle.connect(team).setMysoPrice(100000000) @@ -266,6 +291,11 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const rplCollMysoLoanPostPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPostPrice = await mysoOracle.getPrice(meth, myso) const usdcCollMysoLoanPostPrice = await mysoOracle.getPrice(usdc, myso) + const mysoCollWethLoanPostPrice = await mysoOracle.getPrice(myso, weth.address) + const mysoCollWstEthLoanPostPrice = await mysoOracle.getPrice(myso, wsteth.address) + const mysoCollUsdcLoanPostPrice = await mysoOracle.getPrice(myso, usdc) + const mysoCollUsdtLoanPostPrice = await mysoOracle.getPrice(myso, usdt) + const mysoCollDaiLoanPostPrice = await mysoOracle.getPrice(myso, dai) if (showLogs) { console.log( @@ -303,6 +333,11 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(rplCollMysoLoanPostPrice, 18)) console.log(ethers.utils.formatUnits(methCollMysoLoanPostPrice, 18)) console.log(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollWethLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPostPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPostPrice, 6)) + console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPostPrice, 6)) + console.log(ethers.utils.formatUnits(mysoCollDaiLoanPostPrice, 18)) } }) }) From 8f5e125b87c8015f1414816a1f20b2abad4e0ab8 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Thu, 21 Mar 2024 10:20:08 -0400 Subject: [PATCH 07/42] removed comments, shortened update time, updated tests, condensed conditional --- .../oracles/custom/MysoOracle.sol | 68 ++++++++----------- .../mainnet-myso-oracle-forked-tests.ts | 11 ++- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index e6522a8a..2eba590a 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.19; import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; import {IMETH} from "../../interfaces/oracles/IMETH.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -13,7 +12,7 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces */ -contract MysoOracle is ChainlinkBase, Ownable2Step { +contract MysoOracle is ChainlinkBase, Ownable { struct MysoPrice { uint112 prePrice; uint112 postPrice; @@ -22,24 +21,25 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { // solhint-disable var-name-mixedcase address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address - address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // weth + address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant WSTETH = - 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //wsteth - address internal constant METH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; //meth - address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; //rpl + 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + address internal constant METH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; + address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; address internal constant METH_STAKING_CONTRACT = - 0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f; //meth staking contract + 0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f; uint256 internal constant MYSO_IOO_BASE_CURRENCY_UNIT = 1e18; // 18 decimals for ETH based oracles address internal constant ETH_USD_CHAINLINK = - 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; //eth usd chainlink + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address internal constant STETH_ETH_CHAINLINK = - 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; //steth eth chainlink + 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; address internal constant RPL_USD_CHAINLINK = - 0x4E155eD98aFE9034b7A5962f6C84c86d869daA9d; //rpl usd chainlink + 0x4E155eD98aFE9034b7A5962f6C84c86d869daA9d; - uint256 internal constant MYSO_PRICE_TIME_LOCK = 1 hours; + uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; MysoPrice public mysoPrice; + //address public owner; event MysoPriceUpdated( uint112 prePrice, @@ -58,14 +58,18 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { constructor( address[] memory _tokenAddrs, address[] memory _oracleAddrs, - uint112 _mysoUsdPrice - ) ChainlinkBase(_tokenAddrs, _oracleAddrs, MYSO_IOO_BASE_CURRENCY_UNIT) { + uint112 _mysoUsdPrice, + address _owner + ) + ChainlinkBase(_tokenAddrs, _oracleAddrs, MYSO_IOO_BASE_CURRENCY_UNIT) + Ownable() + { mysoPrice = MysoPrice( _mysoUsdPrice, _mysoUsdPrice, uint32(block.timestamp) ); - _transferOwnership(msg.sender); + _transferOwnership(_owner); } /** @@ -77,33 +81,15 @@ contract MysoOracle is ChainlinkBase, Ownable2Step { function setMysoPrice(uint112 _newMysoUsdPrice) external onlyOwner { MysoPrice memory currMysoPrice = mysoPrice; uint32 newTimeStamp = uint32(block.timestamp + MYSO_PRICE_TIME_LOCK); - if (block.timestamp < currMysoPrice.switchTime) { - // if the switchTime has not yet passed, update only postPrice with new price, - // leave prePrice the same and update switchTime - mysoPrice = MysoPrice( - currMysoPrice.prePrice, - _newMysoUsdPrice, - newTimeStamp - ); - emit MysoPriceUpdated( - currMysoPrice.prePrice, - _newMysoUsdPrice, - newTimeStamp - ); - } else { - // if the switchTime has passed (or exactly equal), update the prePrice with postPrice, - // update the postPrice with new price, and update switchTime - mysoPrice = MysoPrice( - mysoPrice.postPrice, - _newMysoUsdPrice, - uint32(block.timestamp + MYSO_PRICE_TIME_LOCK) - ); - emit MysoPriceUpdated( - currMysoPrice.postPrice, - _newMysoUsdPrice, - newTimeStamp - ); - } + // if the switchTime has not yet passed, update only postPrice with new price, + // leave prePrice the same and update switchTime + // else if the switchTime has passed (or exactly equal), update the prePrice with postPrice, + // update the postPrice with new price, and update switchTime + uint112 prePrice = block.timestamp < currMysoPrice.switchTime + ? currMysoPrice.prePrice + : mysoPrice.postPrice; + mysoPrice = MysoPrice(prePrice, _newMysoUsdPrice, newTimeStamp); + emit MysoPriceUpdated(prePrice, _newMysoUsdPrice, newTimeStamp); } function getPrice( diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index ee0b833c..d2b1852e 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -182,7 +182,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { // deploy myso oracle const MysoOracle = await ethers.getContractFactory('MysoOracle') - const mysoOracle = await MysoOracle.connect(team).deploy( + const mysoOracle = await MysoOracle.connect(lender).deploy( [reth, cbeth, usdc, dai, usdt], [ rethToEthChainlinkAddr, @@ -191,7 +191,8 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { daiToEthChainlinkAddr, usdtToEthChainlinkAddr ], - 50000000 + 50000000, + team.address ) await mysoOracle.deployed() @@ -201,6 +202,10 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { expect(mysoPriceData.postPrice).to.equal(50000000) const timestampAtDeployment = mysoPriceData.switchTime + const mysoOracleOwner = await mysoOracle.owner() + + expect(mysoOracleOwner).to.equal(team.address) + await expect(mysoOracle.connect(lender).setMysoPrice(80000000)).to.be.revertedWith('Ownable: caller is not the owner') await expect(mysoOracle.getPrice(weth.address, cbeth)).to.be.revertedWithCustomError(mysoOracle, 'NoMyso') @@ -272,7 +277,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const newMysoPriceData = await mysoOracle.mysoPrice() expect(newMysoPriceData.prePrice).to.equal(50000000) expect(newMysoPriceData.postPrice).to.equal(100000000) - expect(newMysoPriceData.switchTime).to.be.gte(ethers.BigNumber.from(timestampAtDeployment).add(ONE_HOUR)) + expect(newMysoPriceData.switchTime).to.be.gte(ethers.BigNumber.from(timestampAtDeployment).add(ONE_HOUR.div(12))) const newWethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) expect(newWethCollMysoLoanPrice).to.equal(wethCollMysoLoanPrice) await ethers.provider.send('evm_mine', [ethers.BigNumber.from(newMysoPriceData.switchTime).add(10).toNumber()]) From e7d218f503f2b0240413ac47f7cb7525d3aca084 Mon Sep 17 00:00:00 2001 From: jpick713 <54317750+jpick713@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:12:36 -0400 Subject: [PATCH 08/42] remove unused variable commented out --- contracts/peer-to-peer/oracles/custom/MysoOracle.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 2eba590a..82ce1a4c 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -39,7 +39,6 @@ contract MysoOracle is ChainlinkBase, Ownable { uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; MysoPrice public mysoPrice; - //address public owner; event MysoPriceUpdated( uint112 prePrice, From 9e84949ebe98806bf848b4f9680a216b50461893 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 26 Mar 2024 22:15:29 -0400 Subject: [PATCH 09/42] added ankrEth to oracle --- .../peer-to-peer/interfaces/oracles/IANKRETH.sol | 11 +++++++++++ contracts/peer-to-peer/oracles/custom/MysoOracle.sol | 5 +++++ test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts | 11 +++++++++++ 3 files changed, 27 insertions(+) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IANKRETH.sol diff --git a/contracts/peer-to-peer/interfaces/oracles/IANKRETH.sol b/contracts/peer-to-peer/interfaces/oracles/IANKRETH.sol new file mode 100644 index 00000000..b8025f32 --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IANKRETH.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IANKRETH { + /** + * @notice gets amount of Eth for given ankrEth + * @param amount of ankrEth + * @return amount of eth + */ + function sharesToBonds(uint256 amount) external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 82ce1a4c..998e988b 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.19; import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; +import {IANKRETH} from "../../interfaces/oracles/IANKRETH.sol"; import {IMETH} from "../../interfaces/oracles/IMETH.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -26,6 +27,8 @@ contract MysoOracle is ChainlinkBase, Ownable { 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address internal constant METH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; + address internal constant ANKRETH = + 0xE95A203B1a91a908F9B9CE46459d101078c2c3cb; address internal constant METH_STAKING_CONTRACT = 0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f; uint256 internal constant MYSO_IOO_BASE_CURRENCY_UNIT = 1e18; // 18 decimals for ETH based oracles @@ -139,6 +142,8 @@ contract MysoOracle is ChainlinkBase, Ownable { tokenPriceRaw = IMETH(METH_STAKING_CONTRACT).mETHToETH(1e18); } else if (token == RPL) { tokenPriceRaw = _getRPLPriceInEth(); + } else if (token == ANKRETH) { + tokenPriceRaw = IANKRETH(ANKRETH).sharesToBonds(1e18); } else { tokenPriceRaw = super._getPriceOfToken(token); } diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index d2b1852e..214dfc69 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -175,6 +175,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' const dai = '0x6B175474E89094C44Da98b954EedeAC495271d0F' const usdt = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + const ankreth = '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb' const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' const usdtToEthChainlinkAddr = '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46' @@ -219,6 +220,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const daiCollMysoLoanPrice = await mysoOracle.getPrice(dai, myso) const rplCollMysoLoanPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPrice = await mysoOracle.getPrice(meth, myso) + const ankrethCollMysoLoanPrice = await mysoOracle.getPrice(ankreth, myso) const mysoCollWethLoanPrice = await mysoOracle.getPrice(myso, weth.address) const mysoCollWstEthLoanPrice = await mysoOracle.getPrice(myso, wsteth.address) @@ -257,6 +259,10 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { 'usdcCollMysoLoanPrice', Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 ) + console.log( + 'ankrethCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(ankrethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) console.log(ethers.utils.formatUnits(wethCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(wstEthCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(rethCollMysoLoanPrice, 18)) @@ -296,6 +302,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const rplCollMysoLoanPostPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPostPrice = await mysoOracle.getPrice(meth, myso) const usdcCollMysoLoanPostPrice = await mysoOracle.getPrice(usdc, myso) + const ankrethCollMysoLoanPostPrice = await mysoOracle.getPrice(ankreth, myso) const mysoCollWethLoanPostPrice = await mysoOracle.getPrice(myso, weth.address) const mysoCollWstEthLoanPostPrice = await mysoOracle.getPrice(myso, wsteth.address) const mysoCollUsdcLoanPostPrice = await mysoOracle.getPrice(myso, usdc) @@ -331,6 +338,10 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { 'usdcCollMysoLoanPostPrice', Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 ) + console.log( + 'ankrethCollMysoLoanPostPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(ankrethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + ) console.log(ethers.utils.formatUnits(wethCollMysoLoanPostPrice, 18)) console.log(ethers.utils.formatUnits(wstEthCollMysoLoanPostPrice, 18)) console.log(ethers.utils.formatUnits(rethCollMysoLoanPostPrice, 18)) From f22af37f12ba0ca8f759a1985b7342ec0e052853 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Thu, 28 Mar 2024 23:10:24 -0400 Subject: [PATCH 10/42] added start of math calculation in test token manager --- .solhint.json | 4 +- .../interfaces/oracles/IMysoTokenManager.sol | 13 ++ .../oracles/custom/MysoOracle.sol | 57 ++--- contracts/test/LogExpMath.sol | 207 ++++++++++++++++++ contracts/test/TestnetTokenManager.sol | 22 ++ .../mainnet-myso-oracle-forked-tests.ts | 4 +- 6 files changed, 264 insertions(+), 43 deletions(-) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol create mode 100644 contracts/test/LogExpMath.sol diff --git a/.solhint.json b/.solhint.json index 314dce00..e3df3b41 100644 --- a/.solhint.json +++ b/.solhint.json @@ -4,12 +4,12 @@ "rules": { "ordering": "error", "no-global-import": "error", - "state-visibility": "error", + "state-visibility": "warn", "imports-on-top": "error", "no-unused-vars": "error", "code-complexity": ["warn", 12], "compiler-version": ["error", "^0.8.19"], - "const-name-snakecase": "error", + "const-name-snakecase": "warn", "event-name-camelcase": "error", "constructor-syntax": "error", "func-name-mixedcase": "off", diff --git a/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol b/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol new file mode 100644 index 00000000..ec0b233b --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IMysoTokenManager { + /** + * @notice gets Myso token price from MysoTokenManager + * @param ethPriceInUsd price of eth in usd + * @return mysotoken price in eth + */ + function getMysoPriceInEth( + uint256 ethPriceInUsd + ) external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 998e988b..16ef2875 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -7,6 +7,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; import {IANKRETH} from "../../interfaces/oracles/IANKRETH.sol"; import {IMETH} from "../../interfaces/oracles/IMETH.sol"; +import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -14,12 +15,6 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER * @dev supports oracles which are compatible with v2v3 or v3 interfaces */ contract MysoOracle is ChainlinkBase, Ownable { - struct MysoPrice { - uint112 prePrice; - uint112 postPrice; - uint32 switchTime; - } - // solhint-disable var-name-mixedcase address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -41,13 +36,9 @@ contract MysoOracle is ChainlinkBase, Ownable { uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; - MysoPrice public mysoPrice; + address public mysoTokenManager; - event MysoPriceUpdated( - uint112 prePrice, - uint112 postPrice, - uint32 switchTime - ); + event MysoTokenManagerUpdated(address newMysoTokenManager); error NoMyso(); @@ -55,43 +46,32 @@ contract MysoOracle is ChainlinkBase, Ownable { * @dev constructor for MysoOracle * @param _tokenAddrs array of token addresses * @param _oracleAddrs array of oracle addresses - * @param _mysoUsdPrice initial price of myso in usd (use 8 decimals like chainlink) (eg. 0.50 USD = 0.5 * 1e8) + * @param _owner owner of the contract + * @param _mysoTokenManager address of myso token manager contract */ constructor( address[] memory _tokenAddrs, address[] memory _oracleAddrs, - uint112 _mysoUsdPrice, - address _owner + address _owner, + address _mysoTokenManager ) ChainlinkBase(_tokenAddrs, _oracleAddrs, MYSO_IOO_BASE_CURRENCY_UNIT) Ownable() { - mysoPrice = MysoPrice( - _mysoUsdPrice, - _mysoUsdPrice, - uint32(block.timestamp) - ); + mysoTokenManager = _mysoTokenManager; _transferOwnership(_owner); } /** - * @dev updates postPrice and switchTime - * only updates prePrice if the switchTime has passed - * @param _newMysoUsdPrice initial price of myso in usd (use 8 decimals like chainlink) (eg. 0.50 USD = 0.5 * 1e8) + * @dev updates myso token manager contract address + * @param _newMysoTokenManager new myso token manager contract address */ - function setMysoPrice(uint112 _newMysoUsdPrice) external onlyOwner { - MysoPrice memory currMysoPrice = mysoPrice; - uint32 newTimeStamp = uint32(block.timestamp + MYSO_PRICE_TIME_LOCK); - // if the switchTime has not yet passed, update only postPrice with new price, - // leave prePrice the same and update switchTime - // else if the switchTime has passed (or exactly equal), update the prePrice with postPrice, - // update the postPrice with new price, and update switchTime - uint112 prePrice = block.timestamp < currMysoPrice.switchTime - ? currMysoPrice.prePrice - : mysoPrice.postPrice; - mysoPrice = MysoPrice(prePrice, _newMysoUsdPrice, newTimeStamp); - emit MysoPriceUpdated(prePrice, _newMysoUsdPrice, newTimeStamp); + function setMysoTokenManager( + address _newMysoTokenManager + ) external onlyOwner { + mysoTokenManager = _newMysoTokenManager; + emit MysoTokenManagerUpdated(_newMysoTokenManager); } function getPrice( @@ -166,13 +146,12 @@ contract MysoOracle is ChainlinkBase, Ownable { view returns (uint256 mysoPriceInEth) { - uint256 mysoPriceInUsd = block.timestamp < mysoPrice.switchTime - ? mysoPrice.prePrice - : mysoPrice.postPrice; uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( ETH_USD_CHAINLINK ); - mysoPriceInEth = Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); + mysoPriceInEth = IMysoTokenManager(mysoTokenManager).getMysoPriceInEth( + ethPriceInUsd + ); } function _getRPLPriceInEth() internal view returns (uint256 rplPriceRaw) { diff --git a/contracts/test/LogExpMath.sol b/contracts/test/LogExpMath.sol new file mode 100644 index 00000000..fdfd17b1 --- /dev/null +++ b/contracts/test/LogExpMath.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +library LogExpMath { + // All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying + // two numbers, and multiply by ONE when dividing them. + + // All arguments and return values are 18 decimal fixed point numbers. + + int256 constant ONE_18 = 1e18; + + // Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the + // case of ln36, 36 decimals. + int256 constant ONE_20 = 1e20; + int256 constant ONE_36 = 1e36; + + // The domain of natural exponentiation is bound by the word size and number of decimals used. + // + // Because internally the result will be stored using 20 decimals, the largest possible result is + // (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221. + // The smallest possible result is 10^(-18), which makes largest negative argument + // ln(10^(-18)) = -41.446531673892822312. + // We use 130.0 and -41.0 to have some safety margin. + int256 constant MAX_NATURAL_EXPONENT = 130e18; + int256 constant MIN_NATURAL_EXPONENT = -41e18; + + // Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point + // 256 bit integer. + int256 constant LN_36_LOWER_BOUND = ONE_18 - 1e17; + int256 constant LN_36_UPPER_BOUND = ONE_18 + 1e17; + + uint256 constant MILD_EXPONENT_BOUND = 2 ** 254 / uint256(ONE_20); + + // 18 decimal constants + int256 constant x0 = 128000000000000000000; // 2ˆ7 + int256 constant a0 = + 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals) + int256 constant x1 = 64000000000000000000; // 2ˆ6 + int256 constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals) + + // 20 decimal constants + int256 constant x2 = 3200000000000000000000; // 2ˆ5 + int256 constant a2 = 7896296018268069516100000000000000; // eˆ(x2) + int256 constant x3 = 1600000000000000000000; // 2ˆ4 + int256 constant a3 = 888611052050787263676000000; // eˆ(x3) + int256 constant x4 = 800000000000000000000; // 2ˆ3 + int256 constant a4 = 298095798704172827474000; // eˆ(x4) + int256 constant x5 = 400000000000000000000; // 2ˆ2 + int256 constant a5 = 5459815003314423907810; // eˆ(x5) + int256 constant x6 = 200000000000000000000; // 2ˆ1 + int256 constant a6 = 738905609893065022723; // eˆ(x6) + int256 constant x7 = 100000000000000000000; // 2ˆ0 + int256 constant a7 = 271828182845904523536; // eˆ(x7) + int256 constant x8 = 50000000000000000000; // 2ˆ-1 + int256 constant a8 = 164872127070012814685; // eˆ(x8) + int256 constant x9 = 25000000000000000000; // 2ˆ-2 + int256 constant a9 = 128402541668774148407; // eˆ(x9) + int256 constant x10 = 12500000000000000000; // 2ˆ-3 + int256 constant a10 = 113314845306682631683; // eˆ(x10) + int256 constant x11 = 6250000000000000000; // 2ˆ-4 + int256 constant a11 = 106449445891785942956; // eˆ(x11) + + /** + * @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent. + * + * Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`. + */ + function exp(int256 x) internal pure returns (int256) { + require( + x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, + "INVALID_EXPONENT" + ); + + if (x < 0) { + // We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it + // fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT). + // Fixed point division requires multiplying by ONE_18. + return ((ONE_18 * ONE_18) / exp(-x)); + } + + // First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n, + // where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7 + // because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the + // decomposition. + // At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this + // decomposition, which will be lower than the smallest x_n. + // exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1. + // We mutate x by subtracting x_n, making it the remainder of the decomposition. + + // The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause + // intermediate overflows. Instead we store them as plain integers, with 0 decimals. + // Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the + // decomposition. + + // For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct + // it and compute the accumulated product. + + int256 firstAN; + if (x >= x0) { + x -= x0; + firstAN = a0; + } else if (x >= x1) { + x -= x1; + firstAN = a1; + } else { + firstAN = 1; // One with no decimal places + } + + // We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the + // smaller terms. + x *= 100; + + // `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point + // one. Recall that fixed point multiplication requires dividing by ONE_20. + int256 product = ONE_20; + + if (x >= x2) { + x -= x2; + product = (product * a2) / ONE_20; + } + if (x >= x3) { + x -= x3; + product = (product * a3) / ONE_20; + } + if (x >= x4) { + x -= x4; + product = (product * a4) / ONE_20; + } + if (x >= x5) { + x -= x5; + product = (product * a5) / ONE_20; + } + if (x >= x6) { + x -= x6; + product = (product * a6) / ONE_20; + } + if (x >= x7) { + x -= x7; + product = (product * a7) / ONE_20; + } + if (x >= x8) { + x -= x8; + product = (product * a8) / ONE_20; + } + if (x >= x9) { + x -= x9; + product = (product * a9) / ONE_20; + } + + // x10 and x11 are unnecessary here since we have high enough precision already. + + // Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series + // expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!). + + int256 seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places. + int256 term; // Each term in the sum, where the nth term is (x^n / n!). + + // The first term is simply x. + term = x; + seriesSum += term; + + // Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number, + // multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not. + + term = ((term * x) / ONE_20) / 2; + seriesSum += term; + + term = ((term * x) / ONE_20) / 3; + seriesSum += term; + + term = ((term * x) / ONE_20) / 4; + seriesSum += term; + + term = ((term * x) / ONE_20) / 5; + seriesSum += term; + + term = ((term * x) / ONE_20) / 6; + seriesSum += term; + + term = ((term * x) / ONE_20) / 7; + seriesSum += term; + + term = ((term * x) / ONE_20) / 8; + seriesSum += term; + + term = ((term * x) / ONE_20) / 9; + seriesSum += term; + + term = ((term * x) / ONE_20) / 10; + seriesSum += term; + + term = ((term * x) / ONE_20) / 11; + seriesSum += term; + + term = ((term * x) / ONE_20) / 12; + seriesSum += term; + + // 12 Taylor terms are sufficient for 18 decimal precision. + + // We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor + // approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply + // all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication), + // and then drop two digits to return an 18 decimal value. + + return (((product * seriesSum) / ONE_20) * firstAN) / 100; + } +} diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 5d92f501..0c0365ee 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -8,6 +8,8 @@ import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; import {Errors} from "../Errors.sol"; import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "./LogExpMath.sol"; contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint8 internal _decimals; @@ -17,6 +19,7 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint256 internal _lenderReward; uint256 internal _vaultCreationReward; uint256 internal constant MAX_SUPPLY = 100_000_000 ether; + uint256 public totalMysoLoanAmount; constructor() ERC20("TYSO", "TYSO") { _decimals = 18; @@ -127,7 +130,26 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { super._transferOwnership(_newOwnerProposal); } + function getMysoPriceInEth( + uint256 ethPriceInUsd + ) public view returns (uint256) { + uint256 mysoPriceInUsd = _getMysoPriceInUsd(); + return Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); + } + function decimals() public view override returns (uint8) { return _decimals; } + + function _getMysoPriceInUsd() internal view returns (uint256) { + uint256 k1 = 63 * 1e8; + uint256 k2 = 80 * 1e18; + uint256 a = 1770; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(totalMysoLoanAmount, a, 1000 * 1e18)) + ) + ) + 1e18; + return k1 + Math.mulDiv(k2, 1e8, denominator); + } } diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index 214dfc69..feb3d2a6 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -192,8 +192,8 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { daiToEthChainlinkAddr, usdtToEthChainlinkAddr ], - 50000000, - team.address + team.address, + borrowerGateway.address ) await mysoOracle.deployed() From 7ced3d92e41c6ff601b42e7046caa1dd30739627 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Fri, 29 Mar 2024 16:14:43 -0400 Subject: [PATCH 11/42] refactored to use total loan value and have calcs in oracle --- .../interfaces/oracles/IMysoTokenManager.sol | 9 +-- .../oracles/custom/MysoOracle.sol | 61 +++++++++++++++++-- .../oracles/custom/utils}/LogExpMath.sol | 0 contracts/test/TestnetTokenManager.sol | 21 ------- .../mainnet-myso-oracle-forked-tests.ts | 33 +++------- 5 files changed, 68 insertions(+), 56 deletions(-) rename contracts/{test => peer-to-peer/oracles/custom/utils}/LogExpMath.sol (100%) diff --git a/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol b/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol index ec0b233b..a9e0e552 100644 --- a/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol +++ b/contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol @@ -3,11 +3,8 @@ pragma solidity 0.8.19; interface IMysoTokenManager { /** - * @notice gets Myso token price from MysoTokenManager - * @param ethPriceInUsd price of eth in usd - * @return mysotoken price in eth + * @notice gets Myso token loan amount from MysoTokenManager + * @return total Myso loan amount up until now */ - function getMysoPriceInEth( - uint256 ethPriceInUsd - ) external view returns (uint256); + function totalMysoLoanAmount() external view returns (uint256); } diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 16ef2875..12e2d167 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -9,12 +9,24 @@ import {IANKRETH} from "../../interfaces/oracles/IANKRETH.sol"; import {IMETH} from "../../interfaces/oracles/IMETH.sol"; import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "./utils/LogExpMath.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces */ contract MysoOracle is ChainlinkBase, Ownable { + struct PriceParams { + // maxPrice is in 8 decimals for chainlink consistency + uint96 maxPrice; + // k is in 18 decimals + // e.g. 8e17 is 0.8 in decimal + uint96 k; + // a and b are in terms of 1000 + // e.g. 1770 is 1.77 in decimal + uint32 a; + uint32 b; + } // solhint-disable var-name-mixedcase address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -38,6 +50,8 @@ contract MysoOracle is ChainlinkBase, Ownable { address public mysoTokenManager; + PriceParams public mysoPriceParams; + event MysoTokenManagerUpdated(address newMysoTokenManager); error NoMyso(); @@ -53,12 +67,17 @@ contract MysoOracle is ChainlinkBase, Ownable { address[] memory _tokenAddrs, address[] memory _oracleAddrs, address _owner, - address _mysoTokenManager + address _mysoTokenManager, + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b ) ChainlinkBase(_tokenAddrs, _oracleAddrs, MYSO_IOO_BASE_CURRENCY_UNIT) Ownable() { mysoTokenManager = _mysoTokenManager; + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); _transferOwnership(_owner); } @@ -74,6 +93,22 @@ contract MysoOracle is ChainlinkBase, Ownable { emit MysoTokenManagerUpdated(_newMysoTokenManager); } + /** + * @dev updates myso price params + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + function setMysoPriceParams( + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) external onlyOwner { + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + } + function getPrice( address collToken, address loanToken @@ -149,9 +184,27 @@ contract MysoOracle is ChainlinkBase, Ownable { uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( ETH_USD_CHAINLINK ); - mysoPriceInEth = IMysoTokenManager(mysoTokenManager).getMysoPriceInEth( - ethPriceInUsd - ); + uint256 _totalMysoLoanAmount = IMysoTokenManager(mysoTokenManager) + .totalMysoLoanAmount(); + uint256 mysoPriceInUsd = _getMysoPriceInUsd(_totalMysoLoanAmount); + mysoPriceInEth = Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); + } + + function _getMysoPriceInUsd( + uint256 totalMysoLoanAmount + ) internal view returns (uint256 mysoPriceInUsd) { + PriceParams memory params = mysoPriceParams; + uint256 maxPrice = uint256(params.maxPrice); + uint256 k = uint256(params.k); + uint256 a = uint256(params.a); + uint256 b = uint256(params.b); + uint256 numerator = k * b; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(totalMysoLoanAmount, a, 1000000000)) + ) + ) + (2 * b - 1000) * 1e15; + mysoPriceInUsd = maxPrice - Math.mulDiv(numerator, 1e5, denominator); } function _getRPLPriceInEth() internal view returns (uint256 rplPriceRaw) { diff --git a/contracts/test/LogExpMath.sol b/contracts/peer-to-peer/oracles/custom/utils/LogExpMath.sol similarity index 100% rename from contracts/test/LogExpMath.sol rename to contracts/peer-to-peer/oracles/custom/utils/LogExpMath.sol diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 0c0365ee..2f3c9b4e 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -8,8 +8,6 @@ import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; import {Errors} from "../Errors.sol"; import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {LogExpMath} from "./LogExpMath.sol"; contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint8 internal _decimals; @@ -130,26 +128,7 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { super._transferOwnership(_newOwnerProposal); } - function getMysoPriceInEth( - uint256 ethPriceInUsd - ) public view returns (uint256) { - uint256 mysoPriceInUsd = _getMysoPriceInUsd(); - return Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); - } - function decimals() public view override returns (uint8) { return _decimals; } - - function _getMysoPriceInUsd() internal view returns (uint256) { - uint256 k1 = 63 * 1e8; - uint256 k2 = 80 * 1e18; - uint256 a = 1770; - uint256 denominator = uint256( - LogExpMath.exp( - int256(Math.mulDiv(totalMysoLoanAmount, a, 1000 * 1e18)) - ) - ) + 1e18; - return k1 + Math.mulDiv(k2, 1e8, denominator); - } } diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index feb3d2a6..c4bdb269 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -193,22 +193,19 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { usdtToEthChainlinkAddr ], team.address, - borrowerGateway.address + borrowerGateway.address, + 63000000, + 800000000000000000, + 1770, + 5300 ) await mysoOracle.deployed() - const mysoPriceData = await mysoOracle.mysoPrice() - - expect(mysoPriceData.prePrice).to.equal(50000000) - expect(mysoPriceData.postPrice).to.equal(50000000) - const timestampAtDeployment = mysoPriceData.switchTime - + const mysoPriceData = await mysoOracle.mysoPriceParams() const mysoOracleOwner = await mysoOracle.owner() expect(mysoOracleOwner).to.equal(team.address) - await expect(mysoOracle.connect(lender).setMysoPrice(80000000)).to.be.revertedWith('Ownable: caller is not the owner') - await expect(mysoOracle.getPrice(weth.address, cbeth)).to.be.revertedWithCustomError(mysoOracle, 'NoMyso') const wethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) @@ -279,22 +276,8 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(mysoCollDaiLoanPrice, 18)) } - await mysoOracle.connect(team).setMysoPrice(100000000) - const newMysoPriceData = await mysoOracle.mysoPrice() - expect(newMysoPriceData.prePrice).to.equal(50000000) - expect(newMysoPriceData.postPrice).to.equal(100000000) - expect(newMysoPriceData.switchTime).to.be.gte(ethers.BigNumber.from(timestampAtDeployment).add(ONE_HOUR.div(12))) - const newWethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) - expect(newWethCollMysoLoanPrice).to.equal(wethCollMysoLoanPrice) - await ethers.provider.send('evm_mine', [ethers.BigNumber.from(newMysoPriceData.switchTime).add(10).toNumber()]) - const wethCollMysoLoanPostPrice = await mysoOracle.getPrice(weth.address, myso) - // difference is very small less than the order of 10^-13 - expect( - wethCollMysoLoanPostPrice - .sub(wethCollMysoLoanPrice.div(2)) - .mul(ethers.BigNumber.from(10).pow(13)) - .div(wethCollMysoLoanPostPrice) - ).to.be.equal(0) + await mysoOracle.connect(team).setMysoPriceParams(70000000, 800000000000000000, 1770, 1000) + const newMysoPriceData = await mysoOracle.mysoPriceParams() const wstEthCollMysoLoanPostPrice = await mysoOracle.getPrice(wsteth.address, myso) const rethCollMysoLoanPostPrice = await mysoOracle.getPrice(reth, myso) From 6e3044d6ff717c613ed106f2433ff2fc494f4ca5 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Fri, 29 Mar 2024 22:11:54 -0400 Subject: [PATCH 12/42] added some constants to test token manager --- contracts/test/TestnetTokenManager.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 2f3c9b4e..f580ec8a 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -17,7 +17,13 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint256 internal _lenderReward; uint256 internal _vaultCreationReward; uint256 internal constant MAX_SUPPLY = 100_000_000 ether; + address internal constant MYSO_TOKEN = + 0x00000000000000000000000000000000DeaDBeef; + uint256 public totalMysoLoanAmount; + address public mysoIOOVault; + + // Token Manager TODO; maybe allow for any vault owner to track loan token amount for vault and any token constructor() ERC20("TYSO", "TYSO") { _decimals = 18; @@ -39,6 +45,9 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { _mint(loan.borrower, _borrowerReward); _mint(lenderVault, _lenderReward); } + if (loan.loanToken == MYSO_TOKEN) { + totalMysoLoanAmount += loan.initLoanAmount; + } } function processP2PCreateVault( From f0caf0c5441f52f7af29a7b28c1d7c54d3053958 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sat, 30 Mar 2024 21:47:35 -0400 Subject: [PATCH 13/42] added setter for IOO vault and included into borrow check --- contracts/test/TestnetTokenManager.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index f580ec8a..29d1f2e9 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -23,7 +23,8 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint256 public totalMysoLoanAmount; address public mysoIOOVault; - // Token Manager TODO; maybe allow for any vault owner to track loan token amount for vault and any token + // TODO: mapping oracleAddr -> vaultAddr -> tokenAddr -> loanAmount + flag for being turned tracked + // This will allow for other IOOs to use a custom oracle with auto-updating price for loan amount if desired constructor() ERC20("TYSO", "TYSO") { _decimals = 18; @@ -45,7 +46,7 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { _mint(loan.borrower, _borrowerReward); _mint(lenderVault, _lenderReward); } - if (loan.loanToken == MYSO_TOKEN) { + if (loan.loanToken == MYSO_TOKEN && lenderVault == mysoIOOVault) { totalMysoLoanAmount += loan.initLoanAmount; } } @@ -124,6 +125,11 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { _vaultCreationReward = vaultCreationReward; } + function setIOOVault(address _mysoIOOVault) external { + _checkOwner(); + mysoIOOVault = _mysoIOOVault; + } + function transferOwnership(address _newOwnerProposal) public override { _checkOwner(); if ( From 059a41e318264bee80682c41bc3f5a2b42ea041d Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sun, 7 Apr 2024 21:53:40 -0400 Subject: [PATCH 14/42] added a test net myso oracle in --- .../oracles/custom/MysoOracle.sol | 4 + contracts/test/TestMysoOracle.sol | 170 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 contracts/test/TestMysoOracle.sol diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 12e2d167..f59b5b0a 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -62,6 +62,10 @@ contract MysoOracle is ChainlinkBase, Ownable { * @param _oracleAddrs array of oracle addresses * @param _owner owner of the contract * @param _mysoTokenManager address of myso token manager contract + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 */ constructor( address[] memory _tokenAddrs, diff --git a/contracts/test/TestMysoOracle.sol b/contracts/test/TestMysoOracle.sol new file mode 100644 index 00000000..80466a3a --- /dev/null +++ b/contracts/test/TestMysoOracle.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IMysoTokenManager} from "../peer-to-peer/interfaces/oracles/IMysoTokenManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "../peer-to-peer/oracles/custom/utils/LogExpMath.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +contract TestnetMysoOracle is Ownable { + struct PriceParams { + // maxPrice is in 8 decimals for chainlink consistency + uint96 maxPrice; + // k is in 18 decimals + // e.g. 8e17 is 0.8 in decimal + uint96 k; + // a and b are in terms of 1000 + // e.g. 1770 is 1.77 in decimal + uint32 a; + uint32 b; + } + // solhint-disable var-name-mixedcase + address internal constant MYSO = 0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; // myso sepolia address + address internal constant WETH = 0x3564c06EfEe0FeB0af5EF59dc5440bC45A265dA5; + address internal constant USDC = 0x2296282e2e2a4158140E3b4B99855ADa4a06a4B8; + address internal constant USDT = 0xdc2E37A79Ee93906c4665e49f82C1b895dFc7092; + address internal constant DAI = 0x40aD85ab1aA2a5bA61a2a026cfe1F4fd859eA188; + + uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; + + address public mysoTokenManager; + + PriceParams public mysoPriceParams; + + event MysoTokenManagerUpdated(address newMysoTokenManager); + + error NoMyso(); + error NoOracle(); + + /** + * @dev constructor for TestMysoOracle + * @param _owner owner of the contract + * @param _mysoTokenManager address of myso token manager contract + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + constructor( + address _owner, + address _mysoTokenManager, + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) Ownable() { + mysoTokenManager = _mysoTokenManager; + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + _transferOwnership(_owner); + } + + /** + * @dev updates myso token manager contract address + * @param _newMysoTokenManager new myso token manager contract address + */ + + function setMysoTokenManager( + address _newMysoTokenManager + ) external onlyOwner { + mysoTokenManager = _newMysoTokenManager; + emit MysoTokenManagerUpdated(_newMysoTokenManager); + } + + /** + * @dev updates myso price params + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + function setMysoPriceParams( + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) external onlyOwner { + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + } + + function getPrice( + address collToken, + address loanToken + ) external view returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = (loanToken == MYSO) + ? 18 + : IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = + (priceOfCollToken * 10 ** loanTokenDecimals) / + priceOfLoanToken; + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + // must have at least one token is MYSO to use this oracle + if (collToken != MYSO && loanToken != MYSO) { + revert NoMyso(); + } + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + + function _getPriceOfToken( + address token + ) internal view virtual returns (uint256 tokenPriceRaw) { + if (token == MYSO) { + tokenPriceRaw = _getMysoPriceInEth(); + } else if (token == WETH) { + tokenPriceRaw = 1e18; + } else if (token == USDC || token == USDT || token == DAI) { + tokenPriceRaw = Math.mulDiv(1e8, 1e18, 3300 * 1e8); + } else { + revert NoOracle(); + } + } + + function _getMysoPriceInEth() + internal + view + returns (uint256 mysoPriceInEth) + { + uint256 ethPriceInUsd = 3300 * 1e8; + uint256 _totalMysoLoanAmount = IMysoTokenManager(mysoTokenManager) + .totalMysoLoanAmount(); + uint256 mysoPriceInUsd = _getMysoPriceInUsd(_totalMysoLoanAmount); + mysoPriceInEth = Math.mulDiv(mysoPriceInUsd, 1e18, ethPriceInUsd); + } + + function _getMysoPriceInUsd( + uint256 totalMysoLoanAmount + ) internal view returns (uint256 mysoPriceInUsd) { + PriceParams memory params = mysoPriceParams; + uint256 maxPrice = uint256(params.maxPrice); + uint256 k = uint256(params.k); + uint256 a = uint256(params.a); + uint256 b = uint256(params.b); + uint256 numerator = k * b; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(totalMysoLoanAmount, a, 1000000000)) + ) + ) + (2 * b - 1000) * 1e15; + mysoPriceInUsd = maxPrice - Math.mulDiv(numerator, 1e5, denominator); + } +} From b29d0c31e4cb1ab95b8083e046db3c5d62a35b49 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 9 Apr 2024 11:45:35 -0400 Subject: [PATCH 15/42] fixed test for oracle with new curve --- .../oracles/custom/MysoOracle.sol | 3 +- contracts/test/TestnetTokenManager.sol | 3 +- .../mainnet-myso-oracle-forked-tests.ts | 72 ++++++++++++------- 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index f59b5b0a..72cbb3c1 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -28,7 +28,8 @@ contract MysoOracle is ChainlinkBase, Ownable { uint32 b; } // solhint-disable var-name-mixedcase - address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address + //address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address + address internal constant MYSO = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 29d1f2e9..f009355d 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -18,7 +18,8 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint256 internal _vaultCreationReward; uint256 internal constant MAX_SUPPLY = 100_000_000 ether; address internal constant MYSO_TOKEN = - 0x00000000000000000000000000000000DeaDBeef; + 0x6B175474E89094C44Da98b954EedeAC495271d0F; + //0x00000000000000000000000000000000DeaDBeef; uint256 public totalMysoLoanAmount; address public mysoIOOVault; diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index c4bdb269..a050779f 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -104,6 +104,29 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const lenderVaultAddr = lenderVaultAddrs[0] const lenderVault = await LenderVaultImplementation.attach(lenderVaultAddr) + // add testnet token manager + const TestnetTokenManager = await ethers.getContractFactory('TestnetTokenManager') + const testnetTokenManager = await TestnetTokenManager.connect(team).deploy() + await testnetTokenManager.connect(team).deployed() + await addressRegistry.connect(team).setWhitelistState([testnetTokenManager.address], 9) + + const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F' + const DAI_HOLDER = '0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf' + const dai = await ethers.getContractAt('IERC20', DAI_ADDRESS) + await ethers.provider.send('hardhat_setBalance', [DAI_HOLDER, '0x56BC75E2D63100000']) + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [DAI_HOLDER] + }) + + const daiHolder = await ethers.getSigner(DAI_HOLDER) + + await dai.connect(daiHolder).transfer(team.address, '50000000000000000000000000') + + const owner = await testnetTokenManager.owner() + + await testnetTokenManager.connect(team).setIOOVault(lenderVaultAddr) + // prepare WETH balance const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' const weth = await ethers.getContractAt('IWETH', WETH_ADDRESS) @@ -143,11 +166,14 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { wsteth, reth, cbeth, + dai, rethToEthChainlinkAddr, cbethToEthChainlinkAddr, lenderVault, lenderVaultFactory, - someUser + lenderVaultAddr, + someUser, + testnetTokenManager } } @@ -164,40 +190,37 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { wsteth, reth, cbeth, + dai, cbethToEthChainlinkAddr, rethToEthChainlinkAddr, - lenderVault + lenderVault, + lenderVaultAddr, + testnetTokenManager } = await setupTest() - const myso = '0x00000000000000000000000000000000DeaDBeef' + //const myso = '0x00000000000000000000000000000000DeaDBeef' + const myso = dai.address const meth = '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa' const rpl = '0xD33526068D116cE69F19A9ee46F0bd304F21A51f' const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' - const dai = '0x6B175474E89094C44Da98b954EedeAC495271d0F' const usdt = '0xdAC17F958D2ee523a2206206994597C13D831ec7' const ankreth = '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb' const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' - const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' + //const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' const usdtToEthChainlinkAddr = '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46' // deploy myso oracle const MysoOracle = await ethers.getContractFactory('MysoOracle') const mysoOracle = await MysoOracle.connect(lender).deploy( - [reth, cbeth, usdc, dai, usdt], - [ - rethToEthChainlinkAddr, - cbethToEthChainlinkAddr, - usdcToEthChainlinkAddr, - daiToEthChainlinkAddr, - usdtToEthChainlinkAddr - ], + [reth, cbeth, usdc, usdt], + [rethToEthChainlinkAddr, cbethToEthChainlinkAddr, usdcToEthChainlinkAddr, usdtToEthChainlinkAddr], team.address, - borrowerGateway.address, - 63000000, - 800000000000000000, + testnetTokenManager.address, + 62000000, + ethers.BigNumber.from('800000000000000000'), 1770, - 5300 + 1000 ) await mysoOracle.deployed() @@ -214,7 +237,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const cbethCollMysoLoanPrice = await mysoOracle.getPrice(cbeth, myso) const usdcCollMysoLoanPrice = await mysoOracle.getPrice(usdc, myso) const usdtCollMysoLoanPrice = await mysoOracle.getPrice(usdt, myso) - const daiCollMysoLoanPrice = await mysoOracle.getPrice(dai, myso) + //const daiCollMysoLoanPrice = await mysoOracle.getPrice(dai, myso) const rplCollMysoLoanPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPrice = await mysoOracle.getPrice(meth, myso) const ankrethCollMysoLoanPrice = await mysoOracle.getPrice(ankreth, myso) @@ -223,7 +246,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const mysoCollWstEthLoanPrice = await mysoOracle.getPrice(myso, wsteth.address) const mysoCollUsdcLoanPrice = await mysoOracle.getPrice(myso, usdc) const mysoCollUsdtLoanPrice = await mysoOracle.getPrice(myso, usdt) - const mysoCollDaiLoanPrice = await mysoOracle.getPrice(myso, dai) + //const mysoCollDaiLoanPrice = await mysoOracle.getPrice(myso, dai) //toggle to show logs const showLogs = true @@ -268,17 +291,18 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(methCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(usdtCollMysoLoanPrice, 18)) - console.log(ethers.utils.formatUnits(daiCollMysoLoanPrice, 18)) + //console.log(ethers.utils.formatUnits(daiCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollWethLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPrice, 6)) - console.log(ethers.utils.formatUnits(mysoCollDaiLoanPrice, 18)) + //console.log(ethers.utils.formatUnits(mysoCollDaiLoanPrice, 18)) } - await mysoOracle.connect(team).setMysoPriceParams(70000000, 800000000000000000, 1770, 1000) + await mysoOracle.connect(team).setMysoPriceParams(70000000, ethers.BigNumber.from('800000000000000000'), 1770, 1000) const newMysoPriceData = await mysoOracle.mysoPriceParams() + const wethCollMysoLoanPostPrice = await mysoOracle.getPrice(weth.address, myso) const wstEthCollMysoLoanPostPrice = await mysoOracle.getPrice(wsteth.address, myso) const rethCollMysoLoanPostPrice = await mysoOracle.getPrice(reth, myso) const cbethCollMysoLoanPostPrice = await mysoOracle.getPrice(cbeth, myso) @@ -290,7 +314,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const mysoCollWstEthLoanPostPrice = await mysoOracle.getPrice(myso, wsteth.address) const mysoCollUsdcLoanPostPrice = await mysoOracle.getPrice(myso, usdc) const mysoCollUsdtLoanPostPrice = await mysoOracle.getPrice(myso, usdt) - const mysoCollDaiLoanPostPrice = await mysoOracle.getPrice(myso, dai) + //const mysoCollDaiLoanPostPrice = await mysoOracle.getPrice(myso, dai) if (showLogs) { console.log( @@ -336,7 +360,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPostPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPostPrice, 6)) console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPostPrice, 6)) - console.log(ethers.utils.formatUnits(mysoCollDaiLoanPostPrice, 18)) + //console.log(ethers.utils.formatUnits(mysoCollDaiLoanPostPrice, 18)) } }) }) From d270927faa4e2b8b9c7d22ed7893f522c9417352 Mon Sep 17 00:00:00 2001 From: asardon Date: Tue, 9 Apr 2024 21:49:33 +0200 Subject: [PATCH 16/42] myt token manager --- ...stMysoOracle.sol => TestnetMysoOracle.sol} | 0 contracts/test/TestnetTokenManager.sol | 113 ++++++++++++------ hardhat.config.ts | 5 + .../dao/manageAddressRegistry.json | 6 +- .../utils/configTestMysoOracle.ts | 42 +++++++ .../utils/deployTestMysoOracle.ts | 39 ++++++ .../utils/deployTestnetTokenManager.ts | 32 +++++ 7 files changed, 199 insertions(+), 38 deletions(-) rename contracts/test/{TestMysoOracle.sol => TestnetMysoOracle.sol} (100%) create mode 100644 scripts/peer-to-peer/utils/configTestMysoOracle.ts create mode 100644 scripts/peer-to-peer/utils/deployTestMysoOracle.ts create mode 100644 scripts/peer-to-peer/utils/deployTestnetTokenManager.ts diff --git a/contracts/test/TestMysoOracle.sol b/contracts/test/TestnetMysoOracle.sol similarity index 100% rename from contracts/test/TestMysoOracle.sol rename to contracts/test/TestnetMysoOracle.sol diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 29d1f2e9..62e6ca86 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -2,35 +2,45 @@ pragma solidity 0.8.19; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; import {Errors} from "../Errors.sol"; import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; +import {ILenderVaultImpl} from "../peer-to-peer/interfaces/ILenderVaultImpl.sol"; -contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { - uint8 internal _decimals; - address internal _vaultCompartmentVictim; - address internal _vaultAddr; - uint256 internal _borrowerReward; - uint256 internal _lenderReward; - uint256 internal _vaultCreationReward; - uint256 internal constant MAX_SUPPLY = 100_000_000 ether; +contract TestnetTokenManager is Ownable2Step, IMysoTokenManager { + using SafeERC20 for IERC20; + struct RewardInfo { + uint128 collThreshold; + uint128 mysoTokenMultiplier; + } address internal constant MYSO_TOKEN = - 0x00000000000000000000000000000000DeaDBeef; - + 0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; + address internal constant STAKED_MYSO_TOKEN = + 0x0A6cBCB5Ac7Fc6B47f06c2cE3E828b6EEBf37B06; + mapping(address => RewardInfo) public rewardInfos; + bool public mysoRewardsActive; uint256 public totalMysoLoanAmount; + uint256 public minMysoStakingRequirement; address public mysoIOOVault; + event RewardInfoSet( + address indexed collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier + ); + event MysoRewardsToggled(bool active); + event MinMysoStakingRequirementSet(uint256 minStakingRequirement); + event IOOVaultSet(address indexed mysoIOOVault); + // TODO: mapping oracleAddr -> vaultAddr -> tokenAddr -> loanAmount + flag for being turned tracked // This will allow for other IOOs to use a custom oracle with auto-updating price for loan amount if desired - constructor() ERC20("TYSO", "TYSO") { - _decimals = 18; - _borrowerReward = 1 ether; - _lenderReward = 1 ether; - _vaultCreationReward = 1 ether; + constructor() { + minMysoStakingRequirement = 10_000 * 1e18; _transferOwnership(msg.sender); } @@ -42,22 +52,38 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { address lenderVault ) external returns (uint128[2] memory applicableProtocolFeeParams) { applicableProtocolFeeParams = currProtocolFeeParams; - if (totalSupply() + _borrowerReward + _lenderReward < MAX_SUPPLY) { - _mint(loan.borrower, _borrowerReward); - _mint(lenderVault, _lenderReward); - } if (loan.loanToken == MYSO_TOKEN && lenderVault == mysoIOOVault) { totalMysoLoanAmount += loan.initLoanAmount; } + if ( + mysoRewardsActive && + IERC20(STAKED_MYSO_TOKEN).balanceOf(loan.borrower) > + minMysoStakingRequirement + ) { + RewardInfo memory rewardInfo = rewardInfos[loan.collToken]; + uint256 rewardAmount = loan.initCollAmount * + rewardInfo.mysoTokenMultiplier; + uint256 bal = IERC20(MYSO_TOKEN).balanceOf(address(this)); + rewardAmount = rewardAmount > bal ? bal : rewardAmount; + if ( + loan.initCollAmount > rewardInfo.collThreshold && + rewardAmount > 0 + ) { + SafeERC20.safeTransfer( + IERC20(MYSO_TOKEN), + ILenderVaultImpl(lenderVault).owner(), + rewardAmount + ); + } + } } + // solhint-disable no-empty-blocks function processP2PCreateVault( uint256 /*numRegisteredVaults*/, address /*vaultCreator*/, - address newLenderVaultAddr - ) external { - _mint(newLenderVaultAddr, _vaultCreationReward); - } + address /*newLenderVaultAddr*/ + ) external {} // solhint-disable no-empty-blocks function processP2PCreateWrappedTokenForERC721s( @@ -114,20 +140,41 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { uint256 /*numLoanProposals*/ ) external {} - function setRewards( - uint256 borrowerReward, - uint256 lenderReward, - uint256 vaultCreationReward + function withdraw(address token, address to, uint256 amount) external { + _checkOwner(); + SafeERC20.safeTransfer(IERC20(token), to, amount); + } + + function setRewardInfo( + address collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier ) external { _checkOwner(); - _borrowerReward = borrowerReward; - _lenderReward = lenderReward; - _vaultCreationReward = vaultCreationReward; + RewardInfo storage rewardInfo = rewardInfos[collToken]; + rewardInfo.collThreshold = collThreshold; + rewardInfo.mysoTokenMultiplier = mysoTokenMultiplier; + emit RewardInfoSet(collToken, collThreshold, mysoTokenMultiplier); + } + + function toggleMysoRewards() external { + _checkOwner(); + mysoRewardsActive = !mysoRewardsActive; + emit MysoRewardsToggled(mysoRewardsActive); + } + + function setMinMysoStakingRequirement( + uint256 _minMysoStakingRequirement + ) external { + _checkOwner(); + minMysoStakingRequirement = _minMysoStakingRequirement; + emit MinMysoStakingRequirementSet(_minMysoStakingRequirement); } function setIOOVault(address _mysoIOOVault) external { _checkOwner(); mysoIOOVault = _mysoIOOVault; + emit IOOVaultSet(_mysoIOOVault); } function transferOwnership(address _newOwnerProposal) public override { @@ -142,8 +189,4 @@ contract TestnetTokenManager is ERC20, Ownable2Step, IMysoTokenManager { } super._transferOwnership(_newOwnerProposal); } - - function decimals() public view override returns (uint8) { - return _decimals; - } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 82dc26f1..2a3caa70 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -156,6 +156,11 @@ const config: HardhatUserConfig = { }, gasReporter: { enabled: true + }, + etherscan: { + apiKey: { + sepolia: process.env.ETHERSCAN_API_KEY + } } } diff --git a/scripts/peer-to-peer/dao/manageAddressRegistry.json b/scripts/peer-to-peer/dao/manageAddressRegistry.json index bf2badda..8ce00f9c 100644 --- a/scripts/peer-to-peer/dao/manageAddressRegistry.json +++ b/scripts/peer-to-peer/dao/manageAddressRegistry.json @@ -20,12 +20,12 @@ ] }, "sepolia": { - "addressRegistry": "0x5EdDDFCAB93b4637b3365E4BB8c69aa1806AE46f", + "addressRegistry": "0x028A90c9834E5e52dB31886D9E05c592b6173F21", "actions": [ { "type": "setWhitelistState", - "addresses": ["0x45b5B91b2f507F881aD42cCb8446bE88BaAfcDAE"], - "state": 1 + "addresses": ["0xC71dBB6b1a9735F3259F0CF746C1c000BF02615c"], + "state": 9 } ] }, diff --git a/scripts/peer-to-peer/utils/configTestMysoOracle.ts b/scripts/peer-to-peer/utils/configTestMysoOracle.ts new file mode 100644 index 00000000..3c2a1b29 --- /dev/null +++ b/scripts/peer-to-peer/utils/configTestMysoOracle.ts @@ -0,0 +1,42 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + const TestnetMysoOracle = await ethers.getContractFactory('TestnetMysoOracle') + const testnetMysoOracleAddr = "0x16B67F0dc94e0fAd995d4dFAB77170d30848c277" + const testnetMysoOracle = await TestnetMysoOracle.attach(testnetMysoOracleAddr) + const mysoTestnetTokenManager = "0xC71dBB6b1a9735F3259F0CF746C1c000BF02615c" + await testnetMysoOracle.connect(deployer).setMysoTokenManager(mysoTestnetTokenManager) + + /* + const maxPrice = "55000000" // 8 decimals + const k = "660000000000000000" // 18 decimals + const a = "1350" // scaled by 1000 + const b = "600" // scaled by 1000 + await testnetMysoOracle.connect(deployer).setMysoPriceParams(maxPrice, k, a, b) + logger.log('testnetMysoOracle deployed at:', testnetMysoOracle.address) + */ +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/utils/deployTestMysoOracle.ts b/scripts/peer-to-peer/utils/deployTestMysoOracle.ts new file mode 100644 index 00000000..e632188a --- /dev/null +++ b/scripts/peer-to-peer/utils/deployTestMysoOracle.ts @@ -0,0 +1,39 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + const TestnetMysoOracle = await ethers.getContractFactory('TestnetMysoOracle') + const owner = deployer.address + const mysoTokenManager = ethers.constants.AddressZero + const maxPrice = "55000000" // 8 decimals + const k = "66000000000000000000" // 18 decimals + const a = "1350" // scaled by 1000 + const b = "600" // scaled by 1000 + const testnetMysoOracle = await TestnetMysoOracle.connect(deployer).deploy(owner, mysoTokenManager, maxPrice, k, a, b) + await testnetMysoOracle.deployed() + logger.log('testnetMysoOracle deployed at:', testnetMysoOracle.address) + // npx hardhat verify 0x16B67F0dc94e0fAd995d4dFAB77170d30848c277 "0xcE3d0e78c15C30ecf631ef529581Af3de0478895" "0x0000000000000000000000000000000000000000" "5500000000000000" "66000000000000000000" "1350" "600" --network sepolia +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts b/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts new file mode 100644 index 00000000..ecc738b7 --- /dev/null +++ b/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts @@ -0,0 +1,32 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + const TestnetTokenManager = await ethers.getContractFactory('TestnetTokenManager') + const testnetTokenManager = await TestnetTokenManager.connect(deployer).deploy() + await testnetTokenManager.deployed() + logger.log('testnetTokenManager deployed at:', testnetTokenManager.address) +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) From 48a513a5be2950c4f611d8864e6f31b4e44fd5f6 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 9 Apr 2024 17:34:27 -0400 Subject: [PATCH 17/42] added borrows and examined price along curve --- .../mainnet-myso-oracle-forked-tests.ts | 262 ++++++++++++++---- 1 file changed, 215 insertions(+), 47 deletions(-) diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index a050779f..aa12fdc0 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -15,6 +15,7 @@ const ONE_WSTETH = ethers.BigNumber.from(10).pow(18) const MAX_UINT128 = ethers.BigNumber.from(2).pow(128).sub(1) const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) const ONE_HOUR = ethers.BigNumber.from(60 * 60) +const ONE_DAY = ethers.BigNumber.from(60 * 60 * 24) const ZERO_ADDR = '0x0000000000000000000000000000000000000000' const ZERO_BYTES32 = ethers.utils.formatBytes32String('') @@ -131,7 +132,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' const weth = await ethers.getContractAt('IWETH', WETH_ADDRESS) await ethers.provider.send('hardhat_setBalance', [borrower.address, '0x204FCE5E3E25026110000000']) - await weth.connect(borrower).deposit({ value: ONE_WETH.mul(1) }) + await weth.connect(borrower).deposit({ value: ONE_WETH.mul(1000) }) //prepare wstEth balances const WSTETH_ADDRESS = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0' @@ -217,9 +218,9 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { [rethToEthChainlinkAddr, cbethToEthChainlinkAddr, usdcToEthChainlinkAddr, usdtToEthChainlinkAddr], team.address, testnetTokenManager.address, - 62000000, - ethers.BigNumber.from('800000000000000000'), - 1770, + 55000000, + ethers.BigNumber.from('660000000000000000'), + 1650, 1000 ) await mysoOracle.deployed() @@ -291,76 +292,243 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(methCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(usdtCollMysoLoanPrice, 18)) - //console.log(ethers.utils.formatUnits(daiCollMysoLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollWethLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPrice, 6)) - //console.log(ethers.utils.formatUnits(mysoCollDaiLoanPrice, 18)) } await mysoOracle.connect(team).setMysoPriceParams(70000000, ethers.BigNumber.from('800000000000000000'), 1770, 1000) const newMysoPriceData = await mysoOracle.mysoPriceParams() + expect(newMysoPriceData[0]).to.equal(70000000) + expect(newMysoPriceData[1]).to.equal(ethers.BigNumber.from('800000000000000000')) + expect(newMysoPriceData[2]).to.equal(1770) + expect(newMysoPriceData[3]).to.equal(1000) + + await mysoOracle.connect(team).setMysoPriceParams(55000000, ethers.BigNumber.from('660000000000000000'), 1650, 1000) + + const initialTotalMysoLoanAmount = await testnetTokenManager.totalMysoLoanAmount() + expect(initialTotalMysoLoanAmount).to.equal(0) + + await dai.connect(team).transfer(lenderVault.address, ONE_MYSO.mul(20000000)) + + await addressRegistry.connect(team).setWhitelistState([mysoOracle.address], 2) + + // lenderVault owner gives quote + const blocknum = await ethers.provider.getBlockNumber() + const timestamp = (await ethers.provider.getBlock(blocknum)).timestamp + let quoteTuples = [ + { + loanPerCollUnitOrLtv: BASE, + interestRatePctInBase: 0, + upfrontFeePctInBase: 0, + tenor: ONE_DAY.mul(180) + } + ] + let onChainQuote = { + generalQuoteInfo: { + collToken: weth.address, + loanToken: myso, + oracleAddr: mysoOracle.address, + minLoan: ONE_MYSO.mul(1000), + maxLoan: MAX_UINT256, + validUntil: timestamp + 600, + earliestRepayTenor: 0, + borrowerCompartmentImplementation: ZERO_ADDR, + isSingleUse: false, + whitelistAddr: ZERO_ADDR, + isWhitelistAddrSingleBorrower: false + }, + quoteTuples: quoteTuples, + salt: ZERO_BYTES32 + } + await addressRegistry.connect(team).setWhitelistState([weth.address, myso], 1) + + await expect(quoteHandler.connect(lender).addOnChainQuote(lenderVault.address, onChainQuote)).to.emit( + quoteHandler, + 'OnChainQuoteAdded' + ) + + // borrower approves borrower gateway + await weth.connect(borrower).approve(borrowerGateway.address, MAX_UINT256) + + // borrow with on chain quote + const collSendAmount = ONE_WETH.mul(50) + const expectedProtocolAndVaultTransferFee = 0 + const expectedCompartmentTransferFee = 0 + const quoteTupleIdx = 0 + const callbackAddr = ZERO_ADDR + const callbackData = ZERO_BYTES32 + + const borrowInstructions = { + collSendAmount, + expectedProtocolAndVaultTransferFee, + expectedCompartmentTransferFee, + deadline: MAX_UINT256, + minLoanAmount: 0, + callbackAddr, + callbackData, + mysoTokenManagerData: ZERO_BYTES32 + } + + const borrowWithOnChainQuoteTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, borrowInstructions, onChainQuote, quoteTupleIdx) + + const borrowWithOnChainQuoteReceipt = await borrowWithOnChainQuoteTransaction.wait() + + const borrowEvent = borrowWithOnChainQuoteReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountFirst = borrowEvent?.args?.loan?.['initRepayAmount'] + const initCollAmountFirst = borrowEvent?.args?.loan?.['initCollAmount'] + + const mysoLoanAmountFirstBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountFirst).to.equal(mysoLoanAmountFirstBorrow) + expect(initCollAmountFirst).to.equal(collSendAmount) + + console.log('mysoLoanAmountFirstBorrow', ethers.utils.formatUnits(mysoLoanAmountFirstBorrow, 18)) + + const wethCollMysoLoanPostFirstBorrowPrice = await mysoOracle.getPrice(weth.address, myso) + const mysoCollUsdcLoanPostFirstBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const usdcCollMysoLoanPostFirstBorrowPrice = await mysoOracle.getPrice(usdc, myso) + expect(wethCollMysoLoanPostFirstBorrowPrice).to.be.lt(wethCollMysoLoanPrice) + expect(usdcCollMysoLoanPostFirstBorrowPrice).to.be.lt(usdcCollMysoLoanPrice) + if (showLogs) { + console.log( + 'usdcCollMysoLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostFirstBorrowPrice, 18) + ) + console.log('usdcCollMysoLoanPrice', ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) + console.log( + 'mysoCollUsdcLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFirstBorrowPrice, 6) + ) + console.log('mysoCollUsdcLoanPrice', ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) + } - const wethCollMysoLoanPostPrice = await mysoOracle.getPrice(weth.address, myso) - const wstEthCollMysoLoanPostPrice = await mysoOracle.getPrice(wsteth.address, myso) - const rethCollMysoLoanPostPrice = await mysoOracle.getPrice(reth, myso) - const cbethCollMysoLoanPostPrice = await mysoOracle.getPrice(cbeth, myso) - const rplCollMysoLoanPostPrice = await mysoOracle.getPrice(rpl, myso) - const methCollMysoLoanPostPrice = await mysoOracle.getPrice(meth, myso) - const usdcCollMysoLoanPostPrice = await mysoOracle.getPrice(usdc, myso) - const ankrethCollMysoLoanPostPrice = await mysoOracle.getPrice(ankreth, myso) - const mysoCollWethLoanPostPrice = await mysoOracle.getPrice(myso, weth.address) - const mysoCollWstEthLoanPostPrice = await mysoOracle.getPrice(myso, wsteth.address) - const mysoCollUsdcLoanPostPrice = await mysoOracle.getPrice(myso, usdc) - const mysoCollUsdtLoanPostPrice = await mysoOracle.getPrice(myso, usdt) - //const mysoCollDaiLoanPostPrice = await mysoOracle.getPrice(myso, dai) + const secondBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, borrowInstructions, onChainQuote, quoteTupleIdx) + const secondBorrowReceipt = await secondBorrowTransaction.wait() + + const secondBorrowEvent = secondBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountSecond = secondBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountSecondBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountSecond).to.equal(mysoLoanAmountSecondBorrow.sub(mysoLoanAmountFirstBorrow)) + + console.log('mysoLoanAmountSecondBorrow', ethers.utils.formatUnits(mysoLoanAmountSecondBorrow, 18)) + + const mysoCollUsdcLoanPostSecondBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const usdcCollMysoLoanPostSecondBorrowPrice = await mysoOracle.getPrice(usdc, myso) if (showLogs) { console.log( - 'wethCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(wethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'usdcCollMysoLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostSecondBorrowPrice, 18) + ) + console.log( + 'usdcCollMysoLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostFirstBorrowPrice, 18) ) console.log( - 'wstEthCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(wstEthCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'mysoCollUsdcLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostSecondBorrowPrice, 6) ) console.log( - 'rethCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(rethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'mysoCollUsdcLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFirstBorrowPrice, 6) ) + } + + const largeBorrowInstructions = { + ...borrowInstructions, + collSendAmount: ONE_WETH.mul(250) + } + + const thirdBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, largeBorrowInstructions, onChainQuote, quoteTupleIdx) + + const thirdBorrowReceipt = await thirdBorrowTransaction.wait() + + const thirdBorrowEvent = thirdBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountThird = thirdBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountThirdBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountThird).to.equal(mysoLoanAmountThirdBorrow.sub(mysoLoanAmountSecondBorrow)) + + console.log('mysoLoanAmountThirdBorrow', ethers.utils.formatUnits(mysoLoanAmountThirdBorrow, 18)) + + const mysoCollUsdcLoanPostThirdBorrowPrice = await mysoOracle.getPrice(myso, usdc) + + if (showLogs) { + console.log( + 'mysoCollUsdcLoanPostThirdBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostThirdBorrowPrice, 6) + ) + console.log( + 'mysoCollUsdcLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostSecondBorrowPrice, 6) + ) + } + + const fourthBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, largeBorrowInstructions, onChainQuote, quoteTupleIdx) + + const fourthBorrowReceipt = await fourthBorrowTransaction.wait() + + const fourthBorrowEvent = fourthBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountFourth = fourthBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountFourthBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountFourth).to.equal(mysoLoanAmountFourthBorrow.sub(mysoLoanAmountThirdBorrow)) + + console.log('mysoLoanAmountFourthBorrow', ethers.utils.formatUnits(mysoLoanAmountFourthBorrow, 18)) + + const mysoCollUsdcLoanPostFourthBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const wethCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(weth.address, myso) + const wstEthCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(wsteth.address, myso) + const rplCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(rpl, myso) + const methCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(meth, myso) + + if (showLogs) { console.log( - 'cbEthCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(cbethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'mysoCollUsdcLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFourthBorrowPrice, 6) ) console.log( - 'rplCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(rplCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'wethCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(wethCollMysoLoanPostFourthBorrowPrice, 18) ) console.log( - 'methCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(methCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'wstEthCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(wstEthCollMysoLoanPostFourthBorrowPrice, 18) ) console.log( - 'usdcCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'rplCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(rplCollMysoLoanPostFourthBorrowPrice, 18) ) console.log( - 'ankrethCollMysoLoanPostPrice', - Math.round(1000000 * Number(ethers.utils.formatUnits(ankrethCollMysoLoanPostPrice, 18).slice(0, 8))) / 1000000 + 'methCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(methCollMysoLoanPostFourthBorrowPrice, 18) ) - console.log(ethers.utils.formatUnits(wethCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(wstEthCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(rethCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(cbethCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(rplCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(methCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(usdcCollMysoLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(mysoCollWethLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPostPrice, 18)) - console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPostPrice, 6)) - console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPostPrice, 6)) - //console.log(ethers.utils.formatUnits(mysoCollDaiLoanPostPrice, 18)) } }) }) From f6e20ddce802e5be462b748b033b1a4d35f168cc Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 9 Apr 2024 21:39:31 -0400 Subject: [PATCH 18/42] added extra note --- contracts/test/TestnetTokenManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 4c2cfc95..bd5cedcd 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -18,8 +18,8 @@ contract TestnetTokenManager is Ownable2Step, IMysoTokenManager { uint128 mysoTokenMultiplier; } address internal constant MYSO_TOKEN = - //0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; // sepolia dev-token - 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Dai testnet stand-in + //0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; // sepolia dev-token toggle if deploying to dev + 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Dai testnet stand-in address internal constant STAKED_MYSO_TOKEN = 0x0A6cBCB5Ac7Fc6B47f06c2cE3E828b6EEBf37B06; mapping(address => RewardInfo) public rewardInfos; From 739b419deb23b43f38216f4f4a81a430009f4190 Mon Sep 17 00:00:00 2001 From: asardon Date: Wed, 10 Apr 2024 20:25:49 +0200 Subject: [PATCH 19/42] updated token manager --- .../tokenManager/DegenScoreDataTypes.sol | 13 ++ contracts/tokenManager/MysoTokenManager.sol | 217 ++++++++++++++++++ .../tokenManager/MysoTokenManagerArbitrum.sol | 57 +++++ .../tokenManager/MysoTokenManagerMainnet.sol | 50 ++++ .../interfaces/IDegenScoreBeaconReader.sol | 16 ++ .../tokenManager/interfaces/IStMysoToken.sol | 7 + 6 files changed, 360 insertions(+) create mode 100644 contracts/tokenManager/DegenScoreDataTypes.sol create mode 100644 contracts/tokenManager/MysoTokenManager.sol create mode 100644 contracts/tokenManager/MysoTokenManagerArbitrum.sol create mode 100644 contracts/tokenManager/MysoTokenManagerMainnet.sol create mode 100644 contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol create mode 100644 contracts/tokenManager/interfaces/IStMysoToken.sol diff --git a/contracts/tokenManager/DegenScoreDataTypes.sol b/contracts/tokenManager/DegenScoreDataTypes.sol new file mode 100644 index 00000000..da15b5bd --- /dev/null +++ b/contracts/tokenManager/DegenScoreDataTypes.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +/// @dev holds data of a Beacon +struct BeaconData { + /// @dev the ID of the Beacon + uint128 beaconId; + /// @dev the timestamp when the Beacon was updated + uint64 updatedAt; + /// @dev the primary Trait IDs of the Beacon holder + uint256[] traitIds; +} diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol new file mode 100644 index 00000000..a00ceb4f --- /dev/null +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; +import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; +import {Errors} from "../Errors.sol"; +import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; +import {ILenderVaultImpl} from "../peer-to-peer/interfaces/ILenderVaultImpl.sol"; +import {IStMysoToken} from "./interfaces/IStMysoToken.sol"; + +contract MysoTokenManager is Ownable2Step, IMysoTokenManager { + using SafeERC20 for IERC20; + struct RewardInfo { + uint128 collThreshold; + uint128 mysoTokenMultiplier; + } + + uint256 public totalMysoLoanAmount; + uint256 public minMysoWeight; + + address public mysoIOOVault; + address public mysoToken; + address public stMysoToken; + address public degenscoreBeaconReader; + mapping(address => RewardInfo) public rewardInfos; + + event RewardInfoSet( + address indexed collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier + ); + event MysoTokenSet(address mysoToken); + event StMysoTokenSet(address stMysoToken); + event MinMysoWeightSet(uint256 minMysoWeight); + event IOOVaultSet(address mysoIOOVault); + + error NotAllowed(); + + constructor( + address _mysoIOOVault, + address _mysoToken, + address _stMysoToken, + uint256 _minMysoWeight + ) { + mysoIOOVault = _mysoIOOVault; + mysoToken = _mysoToken; + stMysoToken = _stMysoToken; + minMysoWeight = _minMysoWeight; + _transferOwnership(msg.sender); + } + + function processP2PBorrow( + uint128[2] memory currProtocolFeeParams, + DataTypesPeerToPeer.BorrowTransferInstructions + calldata /*borrowInstructions*/, + DataTypesPeerToPeer.Loan calldata loan, + address lenderVault + ) external returns (uint128[2] memory applicableProtocolFeeParams) { + applicableProtocolFeeParams = currProtocolFeeParams; + address _mysoToken = mysoToken; + if (_mysoToken != address(0)) { + if (loan.loanToken == _mysoToken && lenderVault == mysoIOOVault) { + totalMysoLoanAmount += loan.initLoanAmount; + } + if (!_isAllowed(loan)) { + revert NotAllowed(); + } + address _stMysoToken = stMysoToken; + if ( + _stMysoToken != address(0) && + IStMysoToken(_stMysoToken).weight(loan.borrower) > minMysoWeight + ) { + RewardInfo memory rewardInfo = rewardInfos[loan.collToken]; + uint256 rewardAmount = loan.initCollAmount * + rewardInfo.mysoTokenMultiplier; + uint256 bal = IERC20(_mysoToken).balanceOf(address(this)); + rewardAmount = rewardAmount > bal ? bal : rewardAmount; + if ( + loan.initCollAmount > rewardInfo.collThreshold && + rewardAmount > 0 + ) { + SafeERC20.safeTransfer( + IERC20(_mysoToken), + ILenderVaultImpl(lenderVault).owner(), + rewardAmount + ); + } + } + } + } + + // solhint-disable no-empty-blocks + function processP2PCreateVault( + uint256 /*numRegisteredVaults*/, + address /*vaultCreator*/, + address /*newLenderVaultAddr*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PCreateWrappedTokenForERC721s( + address /*tokenCreator*/, + DataTypesPeerToPeer.WrappedERC721TokenInfo[] + calldata /*tokensToBeWrapped*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PCreateWrappedTokenForERC20s( + address /*tokenCreator*/, + DataTypesPeerToPeer.WrappedERC20TokenInfo[] + calldata /*tokensToBeWrapped*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolDeposit( + address /*fundingPool*/, + address /*depositor*/, + uint256 /*depositAmount*/, + uint256 /*depositLockupDuration*/, + uint256 /*transferFee*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolSubscribe( + address /*fundingPool*/, + address /*subscriber*/, + address /*loanProposal*/, + uint256 /*subscriptionAmount*/, + uint256 /*subscriptionLockupDuration*/, + uint256 /*totalSubscriptions*/, + DataTypesPeerToPool.LoanTerms calldata /*loanTerms*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolLoanFinalization( + address /*loanProposal*/, + address /*fundingPool*/, + address /*arranger*/, + address /*borrower*/, + uint256 /*grossLoanAmount*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolCreateLoanProposal( + address /*fundingPool*/, + address /*proposalCreator*/, + address /*collToken*/, + uint256 /*arrangerFee*/, + uint256 /*numLoanProposals*/ + ) external {} + + function withdraw(address token, address to, uint256 amount) external { + _checkOwner(); + SafeERC20.safeTransfer(IERC20(token), to, amount); + } + + function setRewardInfo( + address collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier + ) external { + _checkOwner(); + RewardInfo storage rewardInfo = rewardInfos[collToken]; + rewardInfo.collThreshold = collThreshold; + rewardInfo.mysoTokenMultiplier = mysoTokenMultiplier; + emit RewardInfoSet(collToken, collThreshold, mysoTokenMultiplier); + } + + function setMinMysoWeight(uint256 _minMysoWeight) external { + _checkOwner(); + minMysoWeight = _minMysoWeight; + emit MinMysoWeightSet(_minMysoWeight); + } + + function setMysoToken(address _mysoToken) external { + _checkOwner(); + mysoToken = _mysoToken; + emit MysoTokenSet(_mysoToken); + } + + function setStMysoToken(address _stMysoToken) external { + _checkOwner(); + stMysoToken = _stMysoToken; + emit StMysoTokenSet(_stMysoToken); + } + + function setIOOVault(address _mysoIOOVault) external { + _checkOwner(); + mysoIOOVault = _mysoIOOVault; + emit IOOVaultSet(_mysoIOOVault); + } + + function transferOwnership(address _newOwnerProposal) public override { + _checkOwner(); + if ( + _newOwnerProposal == address(0) || + _newOwnerProposal == address(this) || + _newOwnerProposal == pendingOwner() || + _newOwnerProposal == owner() + ) { + revert Errors.InvalidNewOwnerProposal(); + } + super._transferOwnership(_newOwnerProposal); + } + + function _isAllowed( + DataTypesPeerToPeer.Loan calldata /*loan*/ + ) internal virtual returns (bool) { + return true; + } +} diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol new file mode 100644 index 00000000..b6d7e583 --- /dev/null +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {MysoTokenManager} from "./MysoTokenManager.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; + +contract MysoTokenManagerArbitrum is MysoTokenManager { + address internal constant GDAI = 0xd85E038593d7A098614721EaE955EC2022B9B91B; + address internal constant GUSDC = + 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; + address internal constant GETH = 0x5977A9682D7AF81D347CFc338c61692163a2784C; + uint256 public gVolume; + uint256 public gVolumeCap; + + event GVolumeCapSet(uint256 gVolumeCap); + + constructor( + address _mysoIOOVault, + address _mysoToken, + address _stMysoToken, + uint256 _minMysoWeight, + uint256 _gVolumeCap + ) + MysoTokenManager( + _mysoIOOVault, + _mysoToken, + _stMysoToken, + _minMysoWeight + ) + { + gVolumeCap = _gVolumeCap; + } + + function setGVolumeCap(uint256 _gVolumeCap) external { + _checkOwner(); + gVolumeCap = _gVolumeCap; + emit GVolumeCapSet(_gVolumeCap); + } + + function _isAllowed( + DataTypesPeerToPeer.Loan calldata loan + ) internal override returns (bool) { + uint256 _gVolume = gVolume; + if (loan.collToken == GDAI || loan.collToken == GUSDC) { + _gVolume += loan.initCollAmount; + } else if (loan.collToken == GETH) { + _gVolume += loan.initCollAmount * 4; + } + if (_gVolume > gVolumeCap) { + return false; + } else { + gVolume = _gVolume; + return true; + } + } +} diff --git a/contracts/tokenManager/MysoTokenManagerMainnet.sol b/contracts/tokenManager/MysoTokenManagerMainnet.sol new file mode 100644 index 00000000..f64f69d4 --- /dev/null +++ b/contracts/tokenManager/MysoTokenManagerMainnet.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {MysoTokenManager} from "./MysoTokenManager.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; +import {BeaconData} from "./DegenScoreDataTypes.sol"; +import {IDegenScoreBeaconReader} from "./interfaces/IDegenScoreBeaconReader.sol"; + +contract MysoTokenManagerMainnet is MysoTokenManager { + event DegenBeaconScoreReaderSet(address degenscoreBeaconReader); + + constructor( + address _mysoIOOVault, + address _mysoToken, + address _stMysoToken, + uint256 _minMysoWeight, + address _degenscoreBeaconReader + ) + MysoTokenManager( + _mysoIOOVault, + _mysoToken, + _stMysoToken, + _minMysoWeight + ) + { + degenscoreBeaconReader = _degenscoreBeaconReader; + } + + function setDegenscoreBeaconReader( + address _degenscoreBeaconReader + ) external { + _checkOwner(); + degenscoreBeaconReader = _degenscoreBeaconReader; + emit DegenBeaconScoreReaderSet(_degenscoreBeaconReader); + } + + function _isAllowed( + DataTypesPeerToPeer.Loan calldata loan + ) internal virtual override returns (bool) { + address _degenscoreBeaconReader = degenscoreBeaconReader; + if (_degenscoreBeaconReader == address(0)) { + return true; + } + BeaconData memory beaconData = IDegenScoreBeaconReader( + _degenscoreBeaconReader + ).beaconDataOf(loan.borrower); + return beaconData.updatedAt != 0; + } +} diff --git a/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol b/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol new file mode 100644 index 00000000..34c8d12c --- /dev/null +++ b/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {BeaconData} from "../DegenScoreDataTypes.sol"; + +interface IDegenScoreBeaconReader { + /** + * @dev returns the Beacon data for a account + * @param account the address of the Beacon holder + * @return beaconData the metadata of a Beacon holder + */ + function beaconDataOf( + address account + ) external view returns (BeaconData memory); +} diff --git a/contracts/tokenManager/interfaces/IStMysoToken.sol b/contracts/tokenManager/interfaces/IStMysoToken.sol new file mode 100644 index 00000000..21b05fa0 --- /dev/null +++ b/contracts/tokenManager/interfaces/IStMysoToken.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +interface IStMysoToken { + function weight(address acc) external returns (uint256); +} From 1eeb59dabb4f60f5e205d09c26c789e53ad24379 Mon Sep 17 00:00:00 2001 From: asardon Date: Wed, 10 Apr 2024 20:48:13 +0200 Subject: [PATCH 20/42] updated token manager --- contracts/test/TestnetTokenManager.sol | 4 +-- .../tokenManager/MysoTokenManagerArbitrum.sol | 36 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/contracts/test/TestnetTokenManager.sol b/contracts/test/TestnetTokenManager.sol index 4c2cfc95..40e82ffc 100644 --- a/contracts/test/TestnetTokenManager.sol +++ b/contracts/test/TestnetTokenManager.sol @@ -18,8 +18,8 @@ contract TestnetTokenManager is Ownable2Step, IMysoTokenManager { uint128 mysoTokenMultiplier; } address internal constant MYSO_TOKEN = - //0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; // sepolia dev-token - 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Dai testnet stand-in + //0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4; // sepolia dev-token + 0x6B175474E89094C44Da98b954EedeAC495271d0F; // Dai testnet stand-in address internal constant STAKED_MYSO_TOKEN = 0x0A6cBCB5Ac7Fc6B47f06c2cE3E828b6EEBf37B06; mapping(address => RewardInfo) public rewardInfos; diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol index b6d7e583..1210af20 100644 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -10,9 +10,10 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { address internal constant GUSDC = 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; address internal constant GETH = 0x5977A9682D7AF81D347CFc338c61692163a2784C; - uint256 public gVolume; + mapping(address => uint256) public gVolume; uint256 public gVolumeCap; + event GVolumeSet(address gToken, uint256 gVolume); event GVolumeCapSet(uint256 gVolumeCap); constructor( @@ -32,6 +33,12 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { gVolumeCap = _gVolumeCap; } + function setGVolume(address _gToken, uint256 _gVolume) external { + _checkOwner(); + gVolume[_gToken] = _gVolume; + emit GVolumeSet(_gToken, _gVolume); + } + function setGVolumeCap(uint256 _gVolumeCap) external { _checkOwner(); gVolumeCap = _gVolumeCap; @@ -40,18 +47,23 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { function _isAllowed( DataTypesPeerToPeer.Loan calldata loan - ) internal override returns (bool) { - uint256 _gVolume = gVolume; - if (loan.collToken == GDAI || loan.collToken == GUSDC) { - _gVolume += loan.initCollAmount; - } else if (loan.collToken == GETH) { - _gVolume += loan.initCollAmount * 4; - } - if (_gVolume > gVolumeCap) { - return false; + ) internal override returns (bool isAllowed) { + if ( + loan.collToken == GDAI || + loan.collToken == GUSDC || + loan.collToken == GETH + ) { + uint256 _gVolume = gVolume[loan.collToken]; + _gVolume += + (loan.collToken == GETH ? 3500 : 1) * + loan.initCollAmount; + isAllowed = _gVolume <= gVolumeCap; + if (_gVolume <= gVolumeCap) { + isAllowed = true; + gVolume[loan.collToken] = _gVolume; + } } else { - gVolume = _gVolume; - return true; + isAllowed = true; } } } From cabcf896a83beca0ae267580665ecdcd1fdb00ee Mon Sep 17 00:00:00 2001 From: asardon Date: Wed, 10 Apr 2024 21:04:48 +0200 Subject: [PATCH 21/42] updated token manager --- contracts/tokenManager/MysoTokenManagerArbitrum.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol index 1210af20..851d953d 100644 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -58,8 +58,7 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { (loan.collToken == GETH ? 3500 : 1) * loan.initCollAmount; isAllowed = _gVolume <= gVolumeCap; - if (_gVolume <= gVolumeCap) { - isAllowed = true; + if (isAllowed) { gVolume[loan.collToken] = _gVolume; } } else { From 7481fe2e56b6fe18aa7a2be42bac46a6c8467e61 Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 10:01:46 +0200 Subject: [PATCH 22/42] updated token manager --- contracts/tokenManager/MysoTokenManager.sol | 11 ++-- .../tokenManager/MysoTokenManagerArbitrum.sol | 50 ++++++++++++------- .../tokenManager/MysoTokenManagerMainnet.sol | 17 ++++--- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index a00ceb4f..8b3d4a94 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -63,12 +63,14 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { applicableProtocolFeeParams = currProtocolFeeParams; address _mysoToken = mysoToken; if (_mysoToken != address(0)) { - if (loan.loanToken == _mysoToken && lenderVault == mysoIOOVault) { - totalMysoLoanAmount += loan.initLoanAmount; - } - if (!_isAllowed(loan)) { + bool isMysoIoo = loan.loanToken == _mysoToken && + lenderVault == mysoIOOVault; + if (!_isAllowed(isMysoIoo, loan)) { revert NotAllowed(); } + if (isMysoIoo) { + totalMysoLoanAmount += loan.initLoanAmount; + } address _stMysoToken = stMysoToken; if ( _stMysoToken != address(0) && @@ -210,6 +212,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { } function _isAllowed( + bool /*isMysoIoo*/, DataTypesPeerToPeer.Loan calldata /*loan*/ ) internal virtual returns (bool) { return true; diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol index 851d953d..fb345a60 100644 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -11,17 +11,21 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; address internal constant GETH = 0x5977A9682D7AF81D347CFc338c61692163a2784C; mapping(address => uint256) public gVolume; - uint256 public gVolumeCap; + mapping(address => uint256) public gVolumeCaps; event GVolumeSet(address gToken, uint256 gVolume); - event GVolumeCapSet(uint256 gVolumeCap); + event GVolumeCapSet(address gToken, uint256 gVolumeCap); + + error NoGToken(); constructor( address _mysoIOOVault, address _mysoToken, address _stMysoToken, uint256 _minMysoWeight, - uint256 _gVolumeCap + uint256 _gDaiCap, + uint256 _gUsdcCap, + uint256 _gEthCap ) MysoTokenManager( _mysoIOOVault, @@ -30,7 +34,9 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { _minMysoWeight ) { - gVolumeCap = _gVolumeCap; + gVolumeCaps[GDAI] = _gDaiCap; + gVolumeCaps[GUSDC] = _gUsdcCap; + gVolumeCaps[GETH] = _gEthCap; } function setGVolume(address _gToken, uint256 _gVolume) external { @@ -39,27 +45,33 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { emit GVolumeSet(_gToken, _gVolume); } - function setGVolumeCap(uint256 _gVolumeCap) external { + function setGVolumeCap(address gToken, uint256 _gVolumeCap) external { _checkOwner(); - gVolumeCap = _gVolumeCap; - emit GVolumeCapSet(_gVolumeCap); + if (gToken != GDAI && gToken != GDAI && gToken != GDAI) { + revert NoGToken(); + } + gVolumeCaps[gToken] = _gVolumeCap; + emit GVolumeCapSet(gToken, _gVolumeCap); } function _isAllowed( + bool isMysoIoo, DataTypesPeerToPeer.Loan calldata loan ) internal override returns (bool isAllowed) { - if ( - loan.collToken == GDAI || - loan.collToken == GUSDC || - loan.collToken == GETH - ) { - uint256 _gVolume = gVolume[loan.collToken]; - _gVolume += - (loan.collToken == GETH ? 3500 : 1) * - loan.initCollAmount; - isAllowed = _gVolume <= gVolumeCap; - if (isAllowed) { - gVolume[loan.collToken] = _gVolume; + if (isMysoIoo) { + if ( + loan.collToken == GDAI || + loan.collToken == GUSDC || + loan.collToken == GETH + ) { + uint256 _newGVolume = gVolume[loan.collToken] + + loan.initCollAmount; + isAllowed = _newGVolume <= gVolumeCaps[loan.collToken]; + if (isAllowed) { + gVolume[loan.collToken] = _newGVolume; + } + } else { + isAllowed = true; } } else { isAllowed = true; diff --git a/contracts/tokenManager/MysoTokenManagerMainnet.sol b/contracts/tokenManager/MysoTokenManagerMainnet.sol index f64f69d4..cd0d3667 100644 --- a/contracts/tokenManager/MysoTokenManagerMainnet.sol +++ b/contracts/tokenManager/MysoTokenManagerMainnet.sol @@ -36,15 +36,20 @@ contract MysoTokenManagerMainnet is MysoTokenManager { } function _isAllowed( + bool isMysoIoo, DataTypesPeerToPeer.Loan calldata loan ) internal virtual override returns (bool) { - address _degenscoreBeaconReader = degenscoreBeaconReader; - if (_degenscoreBeaconReader == address(0)) { + if (isMysoIoo) { + address _degenscoreBeaconReader = degenscoreBeaconReader; + if (_degenscoreBeaconReader == address(0)) { + return true; + } + BeaconData memory beaconData = IDegenScoreBeaconReader( + _degenscoreBeaconReader + ).beaconDataOf(loan.borrower); + return beaconData.updatedAt != 0; + } else { return true; } - BeaconData memory beaconData = IDegenScoreBeaconReader( - _degenscoreBeaconReader - ).beaconDataOf(loan.borrower); - return beaconData.updatedAt != 0; } } From bdaa74f86b149afbc964ab3d7047cc3edd690110 Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 11:15:38 +0200 Subject: [PATCH 23/42] access via sig --- .../tokenManager/DegenScoreDataTypes.sol | 13 ----- contracts/tokenManager/MysoTokenManager.sol | 42 ++++++++++++-- .../tokenManager/MysoTokenManagerArbitrum.sol | 7 ++- .../tokenManager/MysoTokenManagerMainnet.sol | 55 ------------------- .../interfaces/IDegenScoreBeaconReader.sol | 16 ------ 5 files changed, 42 insertions(+), 91 deletions(-) delete mode 100644 contracts/tokenManager/DegenScoreDataTypes.sol delete mode 100644 contracts/tokenManager/MysoTokenManagerMainnet.sol delete mode 100644 contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol diff --git a/contracts/tokenManager/DegenScoreDataTypes.sol b/contracts/tokenManager/DegenScoreDataTypes.sol deleted file mode 100644 index da15b5bd..00000000 --- a/contracts/tokenManager/DegenScoreDataTypes.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -/// @dev holds data of a Beacon -struct BeaconData { - /// @dev the ID of the Beacon - uint128 beaconId; - /// @dev the timestamp when the Beacon was updated - uint64 updatedAt; - /// @dev the primary Trait IDs of the Beacon holder - uint256[] traitIds; -} diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index 8b3d4a94..8cb07f16 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -4,16 +4,20 @@ pragma solidity 0.8.19; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; import {Errors} from "../Errors.sol"; +import {Helpers} from "../Helpers.sol"; import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; import {ILenderVaultImpl} from "../peer-to-peer/interfaces/ILenderVaultImpl.sol"; import {IStMysoToken} from "./interfaces/IStMysoToken.sol"; contract MysoTokenManager is Ownable2Step, IMysoTokenManager { using SafeERC20 for IERC20; + using ECDSA for bytes32; + struct RewardInfo { uint128 collThreshold; uint128 mysoTokenMultiplier; @@ -27,6 +31,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public stMysoToken; address public degenscoreBeaconReader; mapping(address => RewardInfo) public rewardInfos; + address public signer; event RewardInfoSet( address indexed collToken, @@ -37,6 +42,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { event StMysoTokenSet(address stMysoToken); event MinMysoWeightSet(uint256 minMysoWeight); event IOOVaultSet(address mysoIOOVault); + event SignerSet(address signer); error NotAllowed(); @@ -44,19 +50,21 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address _mysoIOOVault, address _mysoToken, address _stMysoToken, - uint256 _minMysoWeight + uint256 _minMysoWeight, + address _signer ) { mysoIOOVault = _mysoIOOVault; mysoToken = _mysoToken; stMysoToken = _stMysoToken; minMysoWeight = _minMysoWeight; + signer = _signer; _transferOwnership(msg.sender); } function processP2PBorrow( uint128[2] memory currProtocolFeeParams, DataTypesPeerToPeer.BorrowTransferInstructions - calldata /*borrowInstructions*/, + calldata borrowInstructions, DataTypesPeerToPeer.Loan calldata loan, address lenderVault ) external returns (uint128[2] memory applicableProtocolFeeParams) { @@ -65,7 +73,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { if (_mysoToken != address(0)) { bool isMysoIoo = loan.loanToken == _mysoToken && lenderVault == mysoIOOVault; - if (!_isAllowed(isMysoIoo, loan)) { + if (!_isAllowed(isMysoIoo, borrowInstructions, loan)) { revert NotAllowed(); } if (isMysoIoo) { @@ -198,6 +206,12 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { emit IOOVaultSet(_mysoIOOVault); } + function setSigner(address _signer) external { + _checkOwner(); + signer = _signer; + emit SignerSet(_signer); + } + function transferOwnership(address _newOwnerProposal) public override { _checkOwner(); if ( @@ -212,9 +226,25 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { } function _isAllowed( - bool /*isMysoIoo*/, - DataTypesPeerToPeer.Loan calldata /*loan*/ + bool isMysoIoo, + DataTypesPeerToPeer.BorrowTransferInstructions + calldata borrowInstructions, + DataTypesPeerToPeer.Loan calldata loan ) internal virtual returns (bool) { - return true; + address _signer = signer; + if (isMysoIoo && _signer != address(0)) { + bytes32 payloadHash = keccak256(abi.encode(loan.borrower)); + (bytes32 r, bytes32 vs) = Helpers.splitSignature( + borrowInstructions.mysoTokenManagerData + ); + bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); + address recoveredSigner = messageHash.recover(r, vs); + if (recoveredSigner == _signer) { + return true; + } + return false; + } else { + return true; + } } } diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol index fb345a60..b27dc95b 100644 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -23,6 +23,7 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { address _mysoToken, address _stMysoToken, uint256 _minMysoWeight, + address _signer, uint256 _gDaiCap, uint256 _gUsdcCap, uint256 _gEthCap @@ -31,7 +32,8 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { _mysoIOOVault, _mysoToken, _stMysoToken, - _minMysoWeight + _minMysoWeight, + _signer ) { gVolumeCaps[GDAI] = _gDaiCap; @@ -56,8 +58,11 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { function _isAllowed( bool isMysoIoo, + DataTypesPeerToPeer.BorrowTransferInstructions + calldata borrowInstructions, DataTypesPeerToPeer.Loan calldata loan ) internal override returns (bool isAllowed) { + isAllowed = super._isAllowed(isMysoIoo, borrowInstructions, loan); if (isMysoIoo) { if ( loan.collToken == GDAI || diff --git a/contracts/tokenManager/MysoTokenManagerMainnet.sol b/contracts/tokenManager/MysoTokenManagerMainnet.sol deleted file mode 100644 index cd0d3667..00000000 --- a/contracts/tokenManager/MysoTokenManagerMainnet.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.19; - -import {MysoTokenManager} from "./MysoTokenManager.sol"; -import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; -import {BeaconData} from "./DegenScoreDataTypes.sol"; -import {IDegenScoreBeaconReader} from "./interfaces/IDegenScoreBeaconReader.sol"; - -contract MysoTokenManagerMainnet is MysoTokenManager { - event DegenBeaconScoreReaderSet(address degenscoreBeaconReader); - - constructor( - address _mysoIOOVault, - address _mysoToken, - address _stMysoToken, - uint256 _minMysoWeight, - address _degenscoreBeaconReader - ) - MysoTokenManager( - _mysoIOOVault, - _mysoToken, - _stMysoToken, - _minMysoWeight - ) - { - degenscoreBeaconReader = _degenscoreBeaconReader; - } - - function setDegenscoreBeaconReader( - address _degenscoreBeaconReader - ) external { - _checkOwner(); - degenscoreBeaconReader = _degenscoreBeaconReader; - emit DegenBeaconScoreReaderSet(_degenscoreBeaconReader); - } - - function _isAllowed( - bool isMysoIoo, - DataTypesPeerToPeer.Loan calldata loan - ) internal virtual override returns (bool) { - if (isMysoIoo) { - address _degenscoreBeaconReader = degenscoreBeaconReader; - if (_degenscoreBeaconReader == address(0)) { - return true; - } - BeaconData memory beaconData = IDegenScoreBeaconReader( - _degenscoreBeaconReader - ).beaconDataOf(loan.borrower); - return beaconData.updatedAt != 0; - } else { - return true; - } - } -} diff --git a/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol b/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol deleted file mode 100644 index 34c8d12c..00000000 --- a/contracts/tokenManager/interfaces/IDegenScoreBeaconReader.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import {BeaconData} from "../DegenScoreDataTypes.sol"; - -interface IDegenScoreBeaconReader { - /** - * @dev returns the Beacon data for a account - * @param account the address of the Beacon holder - * @return beaconData the metadata of a Beacon holder - */ - function beaconDataOf( - address account - ) external view returns (BeaconData memory); -} From 3ff1f214f89f568e9396e621474f70439a79a085 Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 11:48:29 +0200 Subject: [PATCH 24/42] access via sig --- contracts/tokenManager/MysoTokenManager.sol | 25 +++++++++++-------- .../tokenManager/MysoTokenManagerArbitrum.sol | 3 +++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index 8cb07f16..0ab773c3 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -29,7 +29,6 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public mysoIOOVault; address public mysoToken; address public stMysoToken; - address public degenscoreBeaconReader; mapping(address => RewardInfo) public rewardInfos; address public signer; @@ -231,18 +230,22 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { calldata borrowInstructions, DataTypesPeerToPeer.Loan calldata loan ) internal virtual returns (bool) { - address _signer = signer; - if (isMysoIoo && _signer != address(0)) { - bytes32 payloadHash = keccak256(abi.encode(loan.borrower)); - (bytes32 r, bytes32 vs) = Helpers.splitSignature( - borrowInstructions.mysoTokenManagerData - ); - bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); - address recoveredSigner = messageHash.recover(r, vs); - if (recoveredSigner == _signer) { + if (isMysoIoo) { + address _signer = signer; + if (_signer == address(0)) { return true; + } else { + bytes32 payloadHash = keccak256(abi.encode(loan.borrower)); + (bytes32 r, bytes32 vs) = Helpers.splitSignature( + borrowInstructions.mysoTokenManagerData + ); + bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); + address recoveredSigner = messageHash.recover(r, vs); + if (recoveredSigner == _signer) { + return true; + } + return false; } - return false; } else { return true; } diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol index b27dc95b..df06686b 100644 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -63,6 +63,9 @@ contract MysoTokenManagerArbitrum is MysoTokenManager { DataTypesPeerToPeer.Loan calldata loan ) internal override returns (bool isAllowed) { isAllowed = super._isAllowed(isMysoIoo, borrowInstructions, loan); + if (!isAllowed) { + return isAllowed; + } if (isMysoIoo) { if ( loan.collToken == GDAI || From 75eaa96cca0892a9b50160772c71ec1c9ab7e049 Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 11:54:18 +0200 Subject: [PATCH 25/42] access via sig --- contracts/tokenManager/MysoTokenManager.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index 0ab773c3..f18bd39b 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -42,6 +42,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { event MinMysoWeightSet(uint256 minMysoWeight); event IOOVaultSet(address mysoIOOVault); event SignerSet(address signer); + event TotalMysoLoanAmountSet(uint256 totalMysoLoanAmount); error NotAllowed(); @@ -211,6 +212,12 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { emit SignerSet(_signer); } + function setTotalMysoLoanAmount(uint256 _totalMysoLoanAmount) external { + _checkOwner(); + totalMysoLoanAmount = _totalMysoLoanAmount; + emit TotalMysoLoanAmountSet(_totalMysoLoanAmount); + } + function transferOwnership(address _newOwnerProposal) public override { _checkOwner(); if ( From 22bad1232d6c0c5e09114d9bc0a868b7016f571b Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 14:00:45 +0200 Subject: [PATCH 26/42] added tests --- contracts/test/MockStMysoToken.sol | 41 ++ contracts/tokenManager/MysoTokenManager.sol | 21 +- test/peer-to-peer/local-tests-tokenmanager.ts | 377 ++++++++++++++++++ 3 files changed, 431 insertions(+), 8 deletions(-) create mode 100644 contracts/test/MockStMysoToken.sol create mode 100644 test/peer-to-peer/local-tests-tokenmanager.ts diff --git a/contracts/test/MockStMysoToken.sol b/contracts/test/MockStMysoToken.sol new file mode 100644 index 00000000..14b51fcc --- /dev/null +++ b/contracts/test/MockStMysoToken.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Wrapper} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MockStMysoToken is Ownable, ERC20Wrapper { + uint8 private _decimals; + mapping(address => uint256) public weight; + address public mysoToken; + + constructor( + string memory _name, + string memory _symbol, + IERC20 _mysoToken + ) ERC20(_name, _symbol) ERC20Wrapper(_mysoToken) Ownable() { + _decimals = ERC20(address(_mysoToken)).decimals(); + } + + function depositFor( + address account, + uint256 amount + ) public override returns (bool) { + weight[account] = amount; + return super.depositFor(account, amount); + } + + function withdrawTo( + address account, + uint256 amount + ) public override returns (bool) { + weight[account] -= amount; + return super.withdrawTo(account, amount); + } + + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index f18bd39b..aa408523 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.19; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; @@ -20,6 +21,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { struct RewardInfo { uint128 collThreshold; + // Multiplier in units of BASE uint128 mysoTokenMultiplier; } @@ -29,8 +31,8 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public mysoIOOVault; address public mysoToken; address public stMysoToken; + address public accessSigner; mapping(address => RewardInfo) public rewardInfos; - address public signer; event RewardInfoSet( address indexed collToken, @@ -57,7 +59,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { mysoToken = _mysoToken; stMysoToken = _stMysoToken; minMysoWeight = _minMysoWeight; - signer = _signer; + accessSigner = _signer; _transferOwnership(msg.sender); } @@ -82,15 +84,18 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address _stMysoToken = stMysoToken; if ( _stMysoToken != address(0) && - IStMysoToken(_stMysoToken).weight(loan.borrower) > minMysoWeight + IStMysoToken(_stMysoToken).weight(loan.borrower) >= + minMysoWeight ) { RewardInfo memory rewardInfo = rewardInfos[loan.collToken]; - uint256 rewardAmount = loan.initCollAmount * - rewardInfo.mysoTokenMultiplier; + // @dev: multiplier in BASE cancels out with MYT decimals + uint256 rewardAmount = (loan.initCollAmount * + rewardInfo.mysoTokenMultiplier) / + (10 ** IERC20Metadata(loan.collToken).decimals()); uint256 bal = IERC20(_mysoToken).balanceOf(address(this)); rewardAmount = rewardAmount > bal ? bal : rewardAmount; if ( - loan.initCollAmount > rewardInfo.collThreshold && + loan.initCollAmount >= rewardInfo.collThreshold && rewardAmount > 0 ) { SafeERC20.safeTransfer( @@ -208,7 +213,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { function setSigner(address _signer) external { _checkOwner(); - signer = _signer; + accessSigner = _signer; emit SignerSet(_signer); } @@ -238,7 +243,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { DataTypesPeerToPeer.Loan calldata loan ) internal virtual returns (bool) { if (isMysoIoo) { - address _signer = signer; + address _signer = accessSigner; if (_signer == address(0)) { return true; } else { diff --git a/test/peer-to-peer/local-tests-tokenmanager.ts b/test/peer-to-peer/local-tests-tokenmanager.ts new file mode 100644 index 00000000..e0bf9a91 --- /dev/null +++ b/test/peer-to-peer/local-tests-tokenmanager.ts @@ -0,0 +1,377 @@ +import { expect } from 'chai' +import { ethers } from 'hardhat' +import { StandardMerkleTree } from '@openzeppelin/merkle-tree' +import { LenderVaultImpl, MyERC20 } from '../../typechain-types' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { payloadScheme } from './helpers/abi' +import { setupBorrowerWhitelist, getSlot, findBalanceSlot, encodeGlobalPolicy, encodePairPolicy } from './helpers/misc' +import { HARDHAT_CHAIN_ID_AND_FORKING_CONFIG } from '../../hardhat.config' + +// test config vars +let snapshotId: String // use snapshot id to reset state before each test + +// constants +const hre = require('hardhat') +const BASE = ethers.BigNumber.from(10).pow(18) +const ONE_USDC = ethers.BigNumber.from(10).pow(6) +const ONE_WETH = ethers.BigNumber.from(10).pow(18) +const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) +const ONE_DAY = ethers.BigNumber.from(60 * 60 * 24) +const ZERO_BYTES32 = ethers.utils.formatBytes32String('') +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +async function deployCore(deployer: any) { + // deploy address registry + const AddressRegistry = await ethers.getContractFactory('AddressRegistry') + const addressRegistry = await AddressRegistry.connect(deployer).deploy() + await addressRegistry.deployed() + + // deploy borrower gateway + const BorrowerGateway = await ethers.getContractFactory('BorrowerGateway') + const borrowerGateway = await BorrowerGateway.connect(deployer).deploy(addressRegistry.address) + await borrowerGateway.deployed() + + // deploy quote handler + const QuoteHandler = await ethers.getContractFactory('QuoteHandler') + const quoteHandler = await QuoteHandler.connect(deployer).deploy(addressRegistry.address) + await quoteHandler.deployed() + + // deploy lender vault implementation + const LenderVaultImplementation = await ethers.getContractFactory('LenderVaultImpl') + const lenderVaultImplementation = await LenderVaultImplementation.connect(deployer).deploy() + await lenderVaultImplementation.deployed() + + // deploy LenderVaultFactory + const LenderVaultFactory = await ethers.getContractFactory('LenderVaultFactory') + const lenderVaultFactory = await LenderVaultFactory.connect(deployer).deploy( + addressRegistry.address, + lenderVaultImplementation.address + ) + await lenderVaultFactory.deployed() + + await addressRegistry.connect(deployer).initialize( + lenderVaultFactory.address, + borrowerGateway.address, + quoteHandler.address + ); + + return { + addressRegistry, + borrowerGateway, + quoteHandler, + lenderVaultImplementation, + lenderVaultFactory + }; +} + +async function deployTestTokens(deployer: any) { + const MyERC20 = await ethers.getContractFactory('MyERC20'); + + // deploy MYSO Token (MYT) + const myt = await MyERC20.deploy('MYSO Token', 'MYT', 18); + await myt.deployed(); + + // deploy USDC + const usdc = await MyERC20.connect(deployer).deploy('USDC', 'USDC', 6); + await usdc.deployed(); + + // deploy sMYSO Token (sMYT) + const name = "stMYSO Token"; + const symbol = "stMYT"; + const StMYT = await ethers.getContractFactory('MockStMysoToken'); + const stmyt = await StMYT.deploy(name, symbol, myt.address); + await stmyt.deployed(); + + return { + usdc, + myt, + stmyt + }; +} + +async function createAndFundIoo(deployer: any, myt: any, iooVault: any, usdc: any, quoteHandler: any) { + await myt.mint(deployer.address, ONE_WETH.mul("5000000")); + await myt.connect(deployer).transfer(iooVault.address, ONE_WETH.mul("5000000")); + + // create quote + const blocknum = await ethers.provider.getBlockNumber(); + const timestamp = (await ethers.provider.getBlock(blocknum)).timestamp; + const mytPerUsdc = "4545454545454545920"; + let quoteTuples = [ + { + loanPerCollUnitOrLtv: mytPerUsdc, + interestRatePctInBase: 0, + upfrontFeePctInBase: 0, + tenor: ONE_DAY.mul(60) + } + ]; + const minSize = ONE_WETH.mul("2000"); + const maxSize = ONE_WETH.mul("50000"); + const iooDuration = 60 * 60 * 24 * 5; + let onChainQuote = { + generalQuoteInfo: { + collToken: usdc.address, + loanToken: myt.address, + oracleAddr: ZERO_ADDRESS, + minLoan: minSize, + maxLoan: maxSize, + validUntil: timestamp + iooDuration, + earliestRepayTenor: ONE_DAY, + borrowerCompartmentImplementation: ZERO_ADDRESS, + isSingleUse: false, + whitelistAddr: ZERO_ADDRESS, + isWhitelistAddrSingleBorrower: false + }, + quoteTuples: quoteTuples, + salt: ZERO_BYTES32 + }; + + // add on chain quote + await expect(quoteHandler.connect(deployer).addOnChainQuote(iooVault.address, onChainQuote)).to.emit( + quoteHandler, + 'OnChainQuoteAdded' + ); + return onChainQuote +} + +describe('Peer-to-Peer: Local Tests', function () { + before(async () => { + console.log('Note: Running local tests with the following hardhat chain id config:') + console.log(HARDHAT_CHAIN_ID_AND_FORKING_CONFIG) + if (HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId !== 31337) { + throw new Error( + `Invalid hardhat forking config! Expected 'HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId' to be 31337 but it is '${HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId}'!` + ) + } + }) + + beforeEach(async () => { + snapshotId = await hre.network.provider.send('evm_snapshot') + }) + + afterEach(async () => { + await hre.network.provider.send('evm_revert', [snapshotId]) + }) + + async function setupTest() { + const [deployer, borrower1, borrower2] = await ethers.getSigners() + + // deploy core + const {addressRegistry, borrowerGateway, quoteHandler, lenderVaultImplementation, lenderVaultFactory} = await deployCore(deployer); + + // deploy test tokens + const {usdc, myt, stmyt} = await deployTestTokens(deployer); + + // mint some test tokens + await usdc.mint(borrower1.address, ONE_USDC.mul("2000000")); + await usdc.mint(borrower2.address, ONE_USDC.mul("2000000")); + + // whitelist tokens + await addressRegistry.connect(deployer).setWhitelistState([myt.address, usdc.address], 1) + + // deploy ioo vault + await lenderVaultFactory.connect(deployer).createVault(ZERO_BYTES32) + const lenderVaultAddrs = await addressRegistry.registeredVaults() + const iooVaultAddr = lenderVaultAddrs[0] + const LenderVaultImplementation = await ethers.getContractFactory('LenderVaultImpl') + const iooVault = await LenderVaultImplementation.attach(iooVaultAddr) + + // create and fund IOO + const iooOnChainQuote = await createAndFundIoo(deployer, myt, iooVault, usdc, quoteHandler) + + // deploy token manager + const mysoIOOVault = iooVaultAddr + const mysoToken = myt.address + const stMysoToken = stmyt.address + const minMysoWeight = ONE_WETH.mul("100000") + const signer = deployer.address + const MysoTokenManager = await ethers.getContractFactory('MysoTokenManager') + const mysoTokenManager = await MysoTokenManager.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer) + await mysoTokenManager.deployed() + + // whitelist myso token manager + await addressRegistry.connect(deployer).setWhitelistState([mysoTokenManager.address], 9) + + return { + deployer, + borrower1, + borrower2, + addressRegistry, + borrowerGateway, + quoteHandler, + iooVault, + mysoTokenManager, + usdc, + myt, + stmyt, + iooOnChainQuote + } + } + + describe('Token Manager', function () { + it('Should handle token manager access correctly', async function () { + const { + deployer, + borrower1, + mysoTokenManager, + usdc + } = await setupTest() + + await expect(mysoTokenManager.connect(borrower1).setIOOVault(ZERO_ADDRESS)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setIOOVault(ZERO_ADDRESS) + expect(await mysoTokenManager.mysoIOOVault()).to.be.equal(ZERO_ADDRESS) + + await expect(mysoTokenManager.connect(borrower1).setMinMysoWeight(1)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setMinMysoWeight(1) + expect(await mysoTokenManager.minMysoWeight()).to.be.equal(1) + + await expect(mysoTokenManager.connect(borrower1).setRewardInfo(usdc.address, "1", "2")).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setRewardInfo(usdc.address, "1", "2") + const rewardInfo = await mysoTokenManager.rewardInfos(usdc.address) + expect(rewardInfo.collThreshold).to.be.equal(1) + expect(rewardInfo.mysoTokenMultiplier).to.be.equal(2) + + await expect(mysoTokenManager.connect(borrower1).setSigner(borrower1.address)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setSigner(borrower1.address) + expect(await mysoTokenManager.accessSigner()).to.be.equal(borrower1.address) + + await expect(mysoTokenManager.connect(borrower1).setMysoToken(usdc.address)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setMysoToken(usdc.address) + expect(await mysoTokenManager.mysoToken()).to.be.equal(usdc.address) + + await expect(mysoTokenManager.connect(borrower1).setStMysoToken(usdc.address)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setStMysoToken(usdc.address) + expect(await mysoTokenManager.stMysoToken()).to.be.equal(usdc.address) + + await expect(mysoTokenManager.connect(borrower1).setTotalMysoLoanAmount(123)).to.be.revertedWith("Ownable: caller is not the owner") + await mysoTokenManager.connect(deployer).setTotalMysoLoanAmount(123) + expect(await mysoTokenManager.totalMysoLoanAmount()).to.be.equal(123) + + }) + + it('Should process IOO quote correctly', async function () { + const { + deployer, + borrower1, + borrower2, + addressRegistry, + borrowerGateway, + quoteHandler, + iooVault, + mysoTokenManager, + usdc, + myt, + stmyt, + iooOnChainQuote + } = await setupTest() + + // create signature for borrower + const payload = ethers.utils.defaultAbiCoder.encode( + ['address'], + [borrower1.address] + ) + const payloadHash = ethers.utils.keccak256(payload) + const signature = await deployer.signMessage(ethers.utils.arrayify(payloadHash)) + const sig = ethers.utils.splitSignature(signature) + const compactSig = sig.compact + + // borrowers approves gateway + await usdc.connect(borrower1).approve(borrowerGateway.address, MAX_UINT256) + await usdc.connect(borrower2).approve(borrowerGateway.address, MAX_UINT256) + + // borrow initiation + const collSendAmount = ONE_USDC.mul(10000) + const expectedProtocolAndVaultTransferFee = 0 + const expectedCompartmentTransferFee = 0 + const quoteTupleIdx = 0 + const callbackAddr = ZERO_ADDRESS + const callbackData = ZERO_BYTES32 + const borrowInstructions = { + collSendAmount, + expectedProtocolAndVaultTransferFee, + expectedCompartmentTransferFee, + deadline: MAX_UINT256, + minLoanAmount: 0, + callbackAddr, + callbackData, + mysoTokenManagerData: ZERO_BYTES32 + } + + // check revert if no sig + await expect(borrowerGateway + .connect(borrower1) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWith("invalid signature length") + + // check pass if valid sig + borrowInstructions.mysoTokenManagerData = compactSig + await borrowerGateway + .connect(borrower1) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + let totalMysoLoanAmount1 = await mysoTokenManager.totalMysoLoanAmount() + let borrower1MytBal = await myt.balanceOf(borrower1.address) + expect(borrower1MytBal).to.be.equal(totalMysoLoanAmount1) + + // check revert if unauthorized borrower + await expect(borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManager, "NotAllowed") + + // set signer to address(0) + mysoTokenManager.setSigner(ZERO_ADDRESS) + + // now anyone can borrow + borrowInstructions.mysoTokenManagerData = ZERO_BYTES32 + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + let borrower2MytBal = await myt.balanceOf(borrower2.address) + let totalMysoLoanAmount2 = await mysoTokenManager.totalMysoLoanAmount() + expect(borrower1MytBal.add(borrower2MytBal)).to.be.equal(totalMysoLoanAmount2) + + // check weight booster + const minMysoWeightForRewards = await mysoTokenManager.minMysoWeight() + await myt.mint(borrower1.address, minMysoWeightForRewards) + await myt.connect(borrower1).approve(stmyt.address, MAX_UINT256) + await stmyt.connect(borrower1).depositFor(borrower1.address, minMysoWeightForRewards) + const borrowerWeight = await stmyt.weight(borrower1.address) + expect(borrowerWeight).to.be.equal(minMysoWeightForRewards) + + // set rewards + const collToken = usdc.address + const collThreshold = ONE_USDC.mul(5000) + const mysoTokenMultiplier = ONE_WETH.mul(5).div(100) // in 1e18 units, i.e., 5% of pledge + await mysoTokenManager.setRewardInfo(collToken, collThreshold, mysoTokenMultiplier) + + // borrow with weight + borrowInstructions.collSendAmount = collThreshold + const preDeployerBal1 = await myt.balanceOf(deployer.address) + + // check that borrow passes also if token manager doesn't have enough reward tokens + await borrowerGateway + .connect(borrower1) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + const postDeployerBal1 = await myt.balanceOf(deployer.address) + expect(preDeployerBal1).to.be.equal(0) + expect(postDeployerBal1).to.be.equal(0) + + // fund token manager with rewards and check rewards + await myt.mint(mysoTokenManager.address, ONE_WETH.mul("1000000")) + await borrowerGateway + .connect(borrower1) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + const postDeployerBal2 = await myt.balanceOf(deployer.address) + const rewardAmount = borrowInstructions.collSendAmount.mul(mysoTokenMultiplier).div(ONE_USDC) + expect(rewardAmount).to.be.equal(postDeployerBal2) + + // unset MYT to discontinue volume tracking + let totalMysoLoanAmountPre = await mysoTokenManager.totalMysoLoanAmount() + mysoTokenManager.connect(deployer).setMysoToken(ZERO_ADDRESS) + borrowInstructions.mysoTokenManagerData = ZERO_BYTES32 + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + let totalMysoLoanAmountPost = await mysoTokenManager.totalMysoLoanAmount() + expect(totalMysoLoanAmountPre).to.be.equal(totalMysoLoanAmountPost) + }) + }) +}) From a7e5b07d775d4024a70952aae80a6d461a29cbf4 Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 11 Apr 2024 14:30:23 +0200 Subject: [PATCH 27/42] added tests --- test/peer-to-peer/local-tests-tokenmanager.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/peer-to-peer/local-tests-tokenmanager.ts b/test/peer-to-peer/local-tests-tokenmanager.ts index e0bf9a91..7dbbc0b3 100644 --- a/test/peer-to-peer/local-tests-tokenmanager.ts +++ b/test/peer-to-peer/local-tests-tokenmanager.ts @@ -247,6 +247,7 @@ describe('Peer-to-Peer: Local Tests', function () { await mysoTokenManager.connect(deployer).setTotalMysoLoanAmount(123) expect(await mysoTokenManager.totalMysoLoanAmount()).to.be.equal(123) + await expect(mysoTokenManager.connect(borrower1).withdraw(usdc.address, borrower1.address, 123)).to.be.revertedWith("Ownable: caller is not the owner") }) it('Should process IOO quote correctly', async function () { @@ -372,6 +373,15 @@ describe('Peer-to-Peer: Local Tests', function () { .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) let totalMysoLoanAmountPost = await mysoTokenManager.totalMysoLoanAmount() expect(totalMysoLoanAmountPre).to.be.equal(totalMysoLoanAmountPost) + + // withdraw remaining token rewards + const preBalTokenManager = await myt.balanceOf(mysoTokenManager.address) + const preBalDeployer = await myt.balanceOf(deployer.address) + await mysoTokenManager.connect(deployer).withdraw(myt.address, deployer.address, preBalTokenManager) + const postBalTokenManager = await myt.balanceOf(mysoTokenManager.address) + const postBalDeployer = await myt.balanceOf(deployer.address) + expect(preBalTokenManager.sub(postBalTokenManager)).to.be.equal(postBalDeployer.sub(preBalDeployer)) + expect(postBalTokenManager).to.be.equal(0) }) }) }) From 1d7bda8ffc7e3c1a2753a658c65dbfbb0ca5c447 Mon Sep 17 00:00:00 2001 From: asardon Date: Fri, 12 Apr 2024 18:10:58 +0200 Subject: [PATCH 28/42] added one-off signatures --- contracts/tokenManager/MysoTokenManager.sol | 16 ++++++++++++---- test/peer-to-peer/local-tests-tokenmanager.ts | 18 ++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index aa408523..fdcf30f1 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -33,7 +33,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public stMysoToken; address public accessSigner; mapping(address => RewardInfo) public rewardInfos; - + mapping(bytes32 => bool) internal alreadyClaimed; event RewardInfoSet( address indexed collToken, uint128 collThreshold, @@ -247,13 +247,21 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { if (_signer == address(0)) { return true; } else { - bytes32 payloadHash = keccak256(abi.encode(loan.borrower)); - (bytes32 r, bytes32 vs) = Helpers.splitSignature( - borrowInstructions.mysoTokenManagerData + (bytes memory compactSig, uint256 nonce) = abi.decode( + borrowInstructions.mysoTokenManagerData, + (bytes, uint256) + ); + bytes32 payloadHash = keccak256( + abi.encode(loan.borrower, nonce) ); + if (alreadyClaimed[payloadHash]) { + return false; + } + (bytes32 r, bytes32 vs) = Helpers.splitSignature(compactSig); bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash); address recoveredSigner = messageHash.recover(r, vs); if (recoveredSigner == _signer) { + alreadyClaimed[payloadHash] = true; return true; } return false; diff --git a/test/peer-to-peer/local-tests-tokenmanager.ts b/test/peer-to-peer/local-tests-tokenmanager.ts index 7dbbc0b3..0350cadc 100644 --- a/test/peer-to-peer/local-tests-tokenmanager.ts +++ b/test/peer-to-peer/local-tests-tokenmanager.ts @@ -267,14 +267,19 @@ describe('Peer-to-Peer: Local Tests', function () { } = await setupTest() // create signature for borrower + const nonce = 0 const payload = ethers.utils.defaultAbiCoder.encode( - ['address'], - [borrower1.address] + ['address', 'uint256'], + [borrower1.address, nonce] ) const payloadHash = ethers.utils.keccak256(payload) const signature = await deployer.signMessage(ethers.utils.arrayify(payloadHash)) const sig = ethers.utils.splitSignature(signature) const compactSig = sig.compact + let mysoTokenManagerData = ethers.utils.defaultAbiCoder.encode( + ['bytes', 'uint256'], + [compactSig, nonce] + ) // borrowers approves gateway await usdc.connect(borrower1).approve(borrowerGateway.address, MAX_UINT256) @@ -301,10 +306,10 @@ describe('Peer-to-Peer: Local Tests', function () { // check revert if no sig await expect(borrowerGateway .connect(borrower1) - .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWith("invalid signature length") + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.reverted // check pass if valid sig - borrowInstructions.mysoTokenManagerData = compactSig + borrowInstructions.mysoTokenManagerData = mysoTokenManagerData await borrowerGateway .connect(borrower1) .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) @@ -312,6 +317,11 @@ describe('Peer-to-Peer: Local Tests', function () { let borrower1MytBal = await myt.balanceOf(borrower1.address) expect(borrower1MytBal).to.be.equal(totalMysoLoanAmount1) + // check revert that user can only borrow once with sig + await expect(borrowerGateway + .connect(borrower1) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManager, "NotAllowed") + // check revert if unauthorized borrower await expect(borrowerGateway .connect(borrower2) From bc19f95b63273a164666c443a609d33265f9581a Mon Sep 17 00:00:00 2001 From: asardon Date: Fri, 12 Apr 2024 18:13:24 +0200 Subject: [PATCH 29/42] added public already claimed getter --- contracts/tokenManager/MysoTokenManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index fdcf30f1..ee4e7a81 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -33,7 +33,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public stMysoToken; address public accessSigner; mapping(address => RewardInfo) public rewardInfos; - mapping(bytes32 => bool) internal alreadyClaimed; + mapping(bytes32 => bool) public alreadyClaimed; event RewardInfoSet( address indexed collToken, uint128 collThreshold, From 79f5d11bdc94cc3de335fdcd142a7dce4e853311 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Tue, 16 Apr 2024 10:59:41 -0400 Subject: [PATCH 30/42] added rsweth and fsusd to oracle --- .../interfaces/oracles/IFXUSD.sol | 10 +++++++++ .../interfaces/oracles/IRSWETH.sol | 10 +++++++++ .../oracles/custom/MysoOracle.sol | 22 +++++++++++++++++++ .../mainnet-myso-oracle-forked-tests.ts | 8 +++++++ 4 files changed, 50 insertions(+) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IFXUSD.sol create mode 100644 contracts/peer-to-peer/interfaces/oracles/IRSWETH.sol diff --git a/contracts/peer-to-peer/interfaces/oracles/IFXUSD.sol b/contracts/peer-to-peer/interfaces/oracles/IFXUSD.sol new file mode 100644 index 00000000..1649a4a4 --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IFXUSD.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IFXUSD { + /** + * @notice gets USD NAV in 18 decimals + * @return NAV in USD + */ + function nav() external view returns (uint256); +} diff --git a/contracts/peer-to-peer/interfaces/oracles/IRSWETH.sol b/contracts/peer-to-peer/interfaces/oracles/IRSWETH.sol new file mode 100644 index 00000000..f383ebe8 --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IRSWETH.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IRSWETH { + /** + * @notice gets rswETH to ETH rate + * @return amount of ETH per rswETH + */ + function rswETHToETHRate() external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index 72cbb3c1..dd65497e 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -7,6 +7,8 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IWSTETH} from "../../interfaces/oracles/IWSTETH.sol"; import {IANKRETH} from "../../interfaces/oracles/IANKRETH.sol"; import {IMETH} from "../../interfaces/oracles/IMETH.sol"; +import {IFXUSD} from "../../interfaces/oracles/IFXUSD.sol"; +import {IRSWETH} from "../../interfaces/oracles/IRSWETH.sol"; import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {LogExpMath} from "./utils/LogExpMath.sol"; @@ -34,6 +36,10 @@ contract MysoOracle is ChainlinkBase, Ownable { address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address internal constant METH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; + address internal constant FXUSD = + 0x085780639CC2cACd35E474e71f4d000e2405d8f6; + address internal constant RSWETH = + 0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0; address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; address internal constant ANKRETH = 0xE95A203B1a91a908F9B9CE46459d101078c2c3cb; @@ -164,6 +170,10 @@ contract MysoOracle is ChainlinkBase, Ownable { tokenPriceRaw = _getRPLPriceInEth(); } else if (token == ANKRETH) { tokenPriceRaw = IANKRETH(ANKRETH).sharesToBonds(1e18); + } else if (token == FXUSD) { + tokenPriceRaw = _getFXUSDPriceInEth(); + } else if (token == RSWETH) { + tokenPriceRaw = IRSWETH(RSWETH).rswETHToETHRate(); } else { tokenPriceRaw = super._getPriceOfToken(token); } @@ -221,4 +231,16 @@ contract MysoOracle is ChainlinkBase, Ownable { ); rplPriceRaw = Math.mulDiv(rplPriceInUSD, 1e18, ethPriceInUsd); } + + function _getFXUSDPriceInEth() + internal + view + returns (uint256 fxusdPriceRaw) + { + uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( + ETH_USD_CHAINLINK + ); + uint256 fxusdPriceInUSD = IFXUSD(FXUSD).nav(); + fxusdPriceRaw = Math.mulDiv(fxusdPriceInUSD, 1e8, ethPriceInUsd); + } } diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index aa12fdc0..f010c1ce 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -206,6 +206,8 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' const usdt = '0xdAC17F958D2ee523a2206206994597C13D831ec7' const ankreth = '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb' + const fxusd = '0x085780639CC2cACd35E474e71f4d000e2405d8f6' + const rsweth = '0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0' const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' //const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' const usdtToEthChainlinkAddr = '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46' @@ -242,11 +244,14 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const rplCollMysoLoanPrice = await mysoOracle.getPrice(rpl, myso) const methCollMysoLoanPrice = await mysoOracle.getPrice(meth, myso) const ankrethCollMysoLoanPrice = await mysoOracle.getPrice(ankreth, myso) + const rswethCollMysoLoanPrice = await mysoOracle.getPrice(rsweth, myso) const mysoCollWethLoanPrice = await mysoOracle.getPrice(myso, weth.address) const mysoCollWstEthLoanPrice = await mysoOracle.getPrice(myso, wsteth.address) const mysoCollUsdcLoanPrice = await mysoOracle.getPrice(myso, usdc) const mysoCollUsdtLoanPrice = await mysoOracle.getPrice(myso, usdt) + // const mysoCollFxUsdLoanPrice = await mysoOracle.getPrice(myso, fxusd) + const mysoCollRswethLoanPrice = await mysoOracle.getPrice(myso, rsweth) //const mysoCollDaiLoanPrice = await mysoOracle.getPrice(myso, dai) //toggle to show logs @@ -296,6 +301,9 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { console.log(ethers.utils.formatUnits(mysoCollWstEthLoanPrice, 18)) console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPrice, 6)) + // console.log('myso coll, fxUsd', ethers.utils.formatUnits(mysoCollFxUsdLoanPrice, 18)) + console.log('rsweth coll myso loan', ethers.utils.formatUnits(rswethCollMysoLoanPrice, 18)) + console.log('myso coll rswEth', ethers.utils.formatUnits(mysoCollRswethLoanPrice, 18)) } await mysoOracle.connect(team).setMysoPriceParams(70000000, ethers.BigNumber.from('800000000000000000'), 1770, 1000) From 9a32576ed8dbda9be4d555bb3c48d584c5effdd5 Mon Sep 17 00:00:00 2001 From: asardon Date: Tue, 16 Apr 2024 23:47:50 +0400 Subject: [PATCH 31/42] simplified cap handling for myso token manager and added test --- .../tokenManager/MysoTokenManagerArbitrum.sol | 88 ------------------- .../tokenManager/MysoTokenManagerWithCaps.sol | 69 +++++++++++++++ test/peer-to-peer/local-tests-tokenmanager.ts | 75 ++++++++++++++++ 3 files changed, 144 insertions(+), 88 deletions(-) delete mode 100644 contracts/tokenManager/MysoTokenManagerArbitrum.sol create mode 100644 contracts/tokenManager/MysoTokenManagerWithCaps.sol diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol deleted file mode 100644 index df06686b..00000000 --- a/contracts/tokenManager/MysoTokenManagerArbitrum.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.19; - -import {MysoTokenManager} from "./MysoTokenManager.sol"; -import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; - -contract MysoTokenManagerArbitrum is MysoTokenManager { - address internal constant GDAI = 0xd85E038593d7A098614721EaE955EC2022B9B91B; - address internal constant GUSDC = - 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; - address internal constant GETH = 0x5977A9682D7AF81D347CFc338c61692163a2784C; - mapping(address => uint256) public gVolume; - mapping(address => uint256) public gVolumeCaps; - - event GVolumeSet(address gToken, uint256 gVolume); - event GVolumeCapSet(address gToken, uint256 gVolumeCap); - - error NoGToken(); - - constructor( - address _mysoIOOVault, - address _mysoToken, - address _stMysoToken, - uint256 _minMysoWeight, - address _signer, - uint256 _gDaiCap, - uint256 _gUsdcCap, - uint256 _gEthCap - ) - MysoTokenManager( - _mysoIOOVault, - _mysoToken, - _stMysoToken, - _minMysoWeight, - _signer - ) - { - gVolumeCaps[GDAI] = _gDaiCap; - gVolumeCaps[GUSDC] = _gUsdcCap; - gVolumeCaps[GETH] = _gEthCap; - } - - function setGVolume(address _gToken, uint256 _gVolume) external { - _checkOwner(); - gVolume[_gToken] = _gVolume; - emit GVolumeSet(_gToken, _gVolume); - } - - function setGVolumeCap(address gToken, uint256 _gVolumeCap) external { - _checkOwner(); - if (gToken != GDAI && gToken != GDAI && gToken != GDAI) { - revert NoGToken(); - } - gVolumeCaps[gToken] = _gVolumeCap; - emit GVolumeCapSet(gToken, _gVolumeCap); - } - - function _isAllowed( - bool isMysoIoo, - DataTypesPeerToPeer.BorrowTransferInstructions - calldata borrowInstructions, - DataTypesPeerToPeer.Loan calldata loan - ) internal override returns (bool isAllowed) { - isAllowed = super._isAllowed(isMysoIoo, borrowInstructions, loan); - if (!isAllowed) { - return isAllowed; - } - if (isMysoIoo) { - if ( - loan.collToken == GDAI || - loan.collToken == GUSDC || - loan.collToken == GETH - ) { - uint256 _newGVolume = gVolume[loan.collToken] + - loan.initCollAmount; - isAllowed = _newGVolume <= gVolumeCaps[loan.collToken]; - if (isAllowed) { - gVolume[loan.collToken] = _newGVolume; - } - } else { - isAllowed = true; - } - } else { - isAllowed = true; - } - } -} diff --git a/contracts/tokenManager/MysoTokenManagerWithCaps.sol b/contracts/tokenManager/MysoTokenManagerWithCaps.sol new file mode 100644 index 00000000..ae2e15de --- /dev/null +++ b/contracts/tokenManager/MysoTokenManagerWithCaps.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {MysoTokenManager} from "./MysoTokenManager.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; + +contract MysoTokenManagerWithCaps is MysoTokenManager { + struct CollatCapInfo { + address token; + uint128 maxPledge; + } + + mapping(address => uint256) public collatCaps; + mapping(address => uint256) public totalPledged; + + event CollatCapsSet(CollatCapInfo[] collatCaps); + + constructor( + address _mysoIOOVault, + address _mysoToken, + address _stMysoToken, + uint256 _minMysoWeight, + address _signer, + CollatCapInfo[] memory _collatCaps + ) + MysoTokenManager( + _mysoIOOVault, + _mysoToken, + _stMysoToken, + _minMysoWeight, + _signer + ) + { + for (uint256 i = 0; i < _collatCaps.length; i++) { + collatCaps[_collatCaps[i].token] = _collatCaps[i].maxPledge; + } + } + + function setCollatCaps(CollatCapInfo[] calldata _collatCaps) external { + _checkOwner(); + for (uint256 i = 0; i < _collatCaps.length; i++) { + collatCaps[_collatCaps[i].token] = _collatCaps[i].maxPledge; + } + emit CollatCapsSet(_collatCaps); + } + + function _isAllowed( + bool isMysoIoo, + DataTypesPeerToPeer.BorrowTransferInstructions + calldata borrowInstructions, + DataTypesPeerToPeer.Loan calldata loan + ) internal override returns (bool isAllowed) { + isAllowed = super._isAllowed(isMysoIoo, borrowInstructions, loan); + if (!isAllowed) { + return isAllowed; + } + if (isMysoIoo) { + uint256 _newPledged = totalPledged[loan.collToken] + + loan.initCollAmount; + isAllowed = _newPledged <= collatCaps[loan.collToken]; + if (isAllowed) { + totalPledged[loan.collToken] = _newPledged; + } + } else { + isAllowed = true; + } + } +} diff --git a/test/peer-to-peer/local-tests-tokenmanager.ts b/test/peer-to-peer/local-tests-tokenmanager.ts index 0350cadc..a7fb47c0 100644 --- a/test/peer-to-peer/local-tests-tokenmanager.ts +++ b/test/peer-to-peer/local-tests-tokenmanager.ts @@ -393,5 +393,80 @@ describe('Peer-to-Peer: Local Tests', function () { expect(preBalTokenManager.sub(postBalTokenManager)).to.be.equal(postBalDeployer.sub(preBalDeployer)) expect(postBalTokenManager).to.be.equal(0) }) + + it('Should process caps correctly', async function () { + const { + deployer, + borrower1, + borrower2, + addressRegistry, + borrowerGateway, + quoteHandler, + mysoTokenManager, + iooVault, + usdc, + myt, + stmyt, + iooOnChainQuote + } = await setupTest() + + // deploy token manager with pledge caps + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const collatCaps = [{token: usdc.address, maxPledge: ONE_USDC.mul(10000)}] + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(iooVault.address, myt.address, stmyt.address, 0, ethers.constants.AddressZero, collatCaps) + await mysoTokenManagerWithCaps.deployed() + + // whitelist new myso token manager + await addressRegistry.connect(deployer).setWhitelistState([mysoTokenManager.address], 0) + await addressRegistry.connect(deployer).setWhitelistState([mysoTokenManagerWithCaps.address], 9) + + // borrowers approves gateway + await usdc.connect(borrower1).approve(borrowerGateway.address, MAX_UINT256) + await usdc.connect(borrower2).approve(borrowerGateway.address, MAX_UINT256) + + // borrow initiation + const collSendAmount = ONE_USDC.mul(4000) + const expectedProtocolAndVaultTransferFee = 0 + const expectedCompartmentTransferFee = 0 + const quoteTupleIdx = 0 + const callbackAddr = ZERO_ADDRESS + const callbackData = ZERO_BYTES32 + const borrowInstructions = { + collSendAmount, + expectedProtocolAndVaultTransferFee, + expectedCompartmentTransferFee, + deadline: MAX_UINT256, + minLoanAmount: 0, + callbackAddr, + callbackData, + mysoTokenManagerData: ZERO_BYTES32 + } + + // check 2 borrows should work + borrowInstructions.mysoTokenManagerData = ZERO_BYTES32 + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + await expect(borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManagerWithCaps, "NotAllowed") + + // set cap higher + const newCollatCaps = [{token: usdc.address, maxPledge: ONE_USDC.mul(12000)}] + await mysoTokenManagerWithCaps.setCollatCaps(newCollatCaps) + + // now borrow works again + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) + + // next one fails again + await expect(borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManagerWithCaps, "NotAllowed") + }) }) }) From 298bfa90f5f5413382a26089a5669c7c06cee6e9 Mon Sep 17 00:00:00 2001 From: asardon Date: Wed, 17 Apr 2024 12:39:06 +0400 Subject: [PATCH 32/42] updated default behavior to ignore caps when set to 0 --- contracts/tokenManager/MysoTokenManager.sol | 8 ++++---- contracts/tokenManager/MysoTokenManagerWithCaps.sol | 7 ++++++- test/peer-to-peer/local-tests-tokenmanager.ts | 13 +++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index ee4e7a81..5e1a5099 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -31,7 +31,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address public mysoIOOVault; address public mysoToken; address public stMysoToken; - address public accessSigner; + address public signingAuthority; mapping(address => RewardInfo) public rewardInfos; mapping(bytes32 => bool) public alreadyClaimed; event RewardInfoSet( @@ -59,7 +59,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { mysoToken = _mysoToken; stMysoToken = _stMysoToken; minMysoWeight = _minMysoWeight; - accessSigner = _signer; + signingAuthority = _signer; _transferOwnership(msg.sender); } @@ -213,7 +213,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { function setSigner(address _signer) external { _checkOwner(); - accessSigner = _signer; + signingAuthority = _signer; emit SignerSet(_signer); } @@ -243,7 +243,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { DataTypesPeerToPeer.Loan calldata loan ) internal virtual returns (bool) { if (isMysoIoo) { - address _signer = accessSigner; + address _signer = signingAuthority; if (_signer == address(0)) { return true; } else { diff --git a/contracts/tokenManager/MysoTokenManagerWithCaps.sol b/contracts/tokenManager/MysoTokenManagerWithCaps.sol index ae2e15de..4591ce25 100644 --- a/contracts/tokenManager/MysoTokenManagerWithCaps.sol +++ b/contracts/tokenManager/MysoTokenManagerWithCaps.sol @@ -53,11 +53,16 @@ contract MysoTokenManagerWithCaps is MysoTokenManager { ) internal override returns (bool isAllowed) { isAllowed = super._isAllowed(isMysoIoo, borrowInstructions, loan); if (!isAllowed) { - return isAllowed; + return false; } if (isMysoIoo) { uint256 _newPledged = totalPledged[loan.collToken] + loan.initCollAmount; + uint256 _collatCap = collatCaps[loan.collToken]; + // @dev: default 0 value means no cap + if (_collatCap == 0) { + return true; + } isAllowed = _newPledged <= collatCaps[loan.collToken]; if (isAllowed) { totalPledged[loan.collToken] = _newPledged; diff --git a/test/peer-to-peer/local-tests-tokenmanager.ts b/test/peer-to-peer/local-tests-tokenmanager.ts index a7fb47c0..793132bf 100644 --- a/test/peer-to-peer/local-tests-tokenmanager.ts +++ b/test/peer-to-peer/local-tests-tokenmanager.ts @@ -233,7 +233,7 @@ describe('Peer-to-Peer: Local Tests', function () { await expect(mysoTokenManager.connect(borrower1).setSigner(borrower1.address)).to.be.revertedWith("Ownable: caller is not the owner") await mysoTokenManager.connect(deployer).setSigner(borrower1.address) - expect(await mysoTokenManager.accessSigner()).to.be.equal(borrower1.address) + expect(await mysoTokenManager.signingAuthority()).to.be.equal(borrower1.address) await expect(mysoTokenManager.connect(borrower1).setMysoToken(usdc.address)).to.be.revertedWith("Ownable: caller is not the owner") await mysoTokenManager.connect(deployer).setMysoToken(usdc.address) @@ -466,7 +466,16 @@ describe('Peer-to-Peer: Local Tests', function () { // next one fails again await expect(borrowerGateway .connect(borrower2) - .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManagerWithCaps, "NotAllowed") + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx)).to.be.revertedWithCustomError(mysoTokenManagerWithCaps, "NotAllowed") + + // set cap to zero to disable + const newCollatCaps_ = [{token: usdc.address, maxPledge: 0}] + await mysoTokenManagerWithCaps.setCollatCaps(newCollatCaps_) + + // now borrow works again + await borrowerGateway + .connect(borrower2) + .borrowWithOnChainQuote(iooVault.address, borrowInstructions, iooOnChainQuote, quoteTupleIdx) }) }) }) From f16aa5db864b268081b895b32b476898e9533d62 Mon Sep 17 00:00:00 2001 From: asardon Date: Wed, 17 Apr 2024 13:21:38 +0400 Subject: [PATCH 33/42] updated deployment script --- .../peer-to-peer/dao/manageAddressRegistry.json | 2 +- .../utils/deployTestnetTokenManager.ts | 17 +++++++++++++---- scripts/peer-to-peer/utils/tokenManagerArgs.js | 8 ++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 scripts/peer-to-peer/utils/tokenManagerArgs.js diff --git a/scripts/peer-to-peer/dao/manageAddressRegistry.json b/scripts/peer-to-peer/dao/manageAddressRegistry.json index 8ce00f9c..3875a052 100644 --- a/scripts/peer-to-peer/dao/manageAddressRegistry.json +++ b/scripts/peer-to-peer/dao/manageAddressRegistry.json @@ -24,7 +24,7 @@ "actions": [ { "type": "setWhitelistState", - "addresses": ["0xC71dBB6b1a9735F3259F0CF746C1c000BF02615c"], + "addresses": ["0xc2dDc2330C06E2A7BfE2084a9A4f1A38f83A6B6f"], "state": 9 } ] diff --git a/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts b/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts index ecc738b7..ab869582 100644 --- a/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts +++ b/scripts/peer-to-peer/utils/deployTestnetTokenManager.ts @@ -20,10 +20,19 @@ async function main() { logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) - const TestnetTokenManager = await ethers.getContractFactory('TestnetTokenManager') - const testnetTokenManager = await TestnetTokenManager.connect(deployer).deploy() - await testnetTokenManager.deployed() - logger.log('testnetTokenManager deployed at:', testnetTokenManager.address) + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const mysoIOOVault = "0x060cceb8Cc54BaD7A01E02Ba11EeCE5304314726" + const mysoToken = "0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4" + const stMysoToken = "0x0A6cBCB5Ac7Fc6B47f06c2cE3E828b6EEBf37B06" + const minMysoWeight = 0 + const signer = deployer.address + const collatCaps : any = [] + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer, collatCaps) + await mysoTokenManagerWithCaps.deployed() + logger.log('testnetTokenManager deployed at:', mysoTokenManagerWithCaps.address) + logger.log(`'${mysoIOOVault}' '${mysoToken}' '${stMysoToken}' '${0}' '${signer}' '${collatCaps}'`) + + // npx hardhat verify 0xc2dDc2330C06E2A7BfE2084a9A4f1A38f83A6B6f --constructor-args .\scripts\peer-to-peer\utils\tokenManagerArgs.js --network sepolia } main().catch(error => { diff --git a/scripts/peer-to-peer/utils/tokenManagerArgs.js b/scripts/peer-to-peer/utils/tokenManagerArgs.js new file mode 100644 index 00000000..24f5970b --- /dev/null +++ b/scripts/peer-to-peer/utils/tokenManagerArgs.js @@ -0,0 +1,8 @@ +module.exports = [ + "0x060cceb8Cc54BaD7A01E02Ba11EeCE5304314726", + "0x8fF1307ba7e5FDc3A411d259bAe641e2B1d897c4", + "0x0A6cBCB5Ac7Fc6B47f06c2cE3E828b6EEBf37B06", + 0, + "0xcE3d0e78c15C30ecf631ef529581Af3de0478895", + [] + ]; \ No newline at end of file From d2e3756a3c33f3eab5d80e51b148f65c64304fb1 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sun, 21 Apr 2024 21:38:05 -0400 Subject: [PATCH 34/42] added crvusd to oracle --- .../oracles/custom/MysoOracle.sol | 20 +++++++++++++++++++ .../mainnet-myso-oracle-forked-tests.ts | 3 +++ 2 files changed, 23 insertions(+) diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index dd65497e..fdcf38c1 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -41,6 +41,8 @@ contract MysoOracle is ChainlinkBase, Ownable { address internal constant RSWETH = 0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0; address internal constant RPL = 0xD33526068D116cE69F19A9ee46F0bd304F21A51f; + address internal constant CRVUSD = + 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E; address internal constant ANKRETH = 0xE95A203B1a91a908F9B9CE46459d101078c2c3cb; address internal constant METH_STAKING_CONTRACT = @@ -52,6 +54,8 @@ contract MysoOracle is ChainlinkBase, Ownable { 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; address internal constant RPL_USD_CHAINLINK = 0x4E155eD98aFE9034b7A5962f6C84c86d869daA9d; + address internal constant CRVUSD_USD_CHAINLINK = + 0xEEf0C605546958c1f899b6fB336C20671f9cD49F; uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; @@ -174,6 +178,8 @@ contract MysoOracle is ChainlinkBase, Ownable { tokenPriceRaw = _getFXUSDPriceInEth(); } else if (token == RSWETH) { tokenPriceRaw = IRSWETH(RSWETH).rswETHToETHRate(); + } else if (token == CRVUSD) { + tokenPriceRaw = _getCRVUSDPriceInEth(); } else { tokenPriceRaw = super._getPriceOfToken(token); } @@ -232,6 +238,20 @@ contract MysoOracle is ChainlinkBase, Ownable { rplPriceRaw = Math.mulDiv(rplPriceInUSD, 1e18, ethPriceInUsd); } + function _getCRVUSDPriceInEth() + internal + view + returns (uint256 crvUsdPriceRaw) + { + uint256 crvUsdPriceInUSD = _checkAndReturnLatestRoundData( + (CRVUSD_USD_CHAINLINK) + ); + uint256 ethPriceInUsd = _checkAndReturnLatestRoundData( + ETH_USD_CHAINLINK + ); + crvUsdPriceRaw = Math.mulDiv(crvUsdPriceInUSD, 1e18, ethPriceInUsd); + } + function _getFXUSDPriceInEth() internal view diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index f010c1ce..a9d42185 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -208,6 +208,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { const ankreth = '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb' const fxusd = '0x085780639CC2cACd35E474e71f4d000e2405d8f6' const rsweth = '0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0' + const crvusd = '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E' const usdcToEthChainlinkAddr = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4' //const daiToEthChainlinkAddr = '0x773616E4d11A78F511299002da57A0a94577F1f4' const usdtToEthChainlinkAddr = '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46' @@ -253,6 +254,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { // const mysoCollFxUsdLoanPrice = await mysoOracle.getPrice(myso, fxusd) const mysoCollRswethLoanPrice = await mysoOracle.getPrice(myso, rsweth) //const mysoCollDaiLoanPrice = await mysoOracle.getPrice(myso, dai) + const mysoCollCrvusdLoanPrice = await mysoOracle.getPrice(myso, crvusd) //toggle to show logs const showLogs = true @@ -304,6 +306,7 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { // console.log('myso coll, fxUsd', ethers.utils.formatUnits(mysoCollFxUsdLoanPrice, 18)) console.log('rsweth coll myso loan', ethers.utils.formatUnits(rswethCollMysoLoanPrice, 18)) console.log('myso coll rswEth', ethers.utils.formatUnits(mysoCollRswethLoanPrice, 18)) + console.log('myso coll crvUsd', ethers.utils.formatUnits(mysoCollCrvusdLoanPrice, 18)) } await mysoOracle.connect(team).setMysoPriceParams(70000000, ethers.BigNumber.from('800000000000000000'), 1770, 1000) From c1d245a5515547a49433c90392de8292d59a23c5 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sun, 21 Apr 2024 22:56:13 -0400 Subject: [PATCH 35/42] started adding in arbitrum oracle --- .../interfaces/oracles/IGLPManager.sol | 11 + .../interfaces/oracles/IGTOKEN.sol | 10 + .../oracles/custom/MysoArbitrumUsdOracle.sol | 198 ++++++++++++++++++ .../TestnetTokenManagerArbitrumOracle.sol | 169 +++++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 contracts/peer-to-peer/interfaces/oracles/IGLPManager.sol create mode 100644 contracts/peer-to-peer/interfaces/oracles/IGTOKEN.sol create mode 100644 contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol create mode 100644 contracts/test/TestnetTokenManagerArbitrumOracle.sol diff --git a/contracts/peer-to-peer/interfaces/oracles/IGLPManager.sol b/contracts/peer-to-peer/interfaces/oracles/IGLPManager.sol new file mode 100644 index 00000000..8a17cbc8 --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IGLPManager.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IGLPManager { + /** + * @notice gets price of GLP in USD with 30 decimals + * @param _maximize will pass true + * @return price of GLP in USD + */ + function getPrice(bool _maximize) external view returns (uint256); +} diff --git a/contracts/peer-to-peer/interfaces/oracles/IGTOKEN.sol b/contracts/peer-to-peer/interfaces/oracles/IGTOKEN.sol new file mode 100644 index 00000000..11cfeb18 --- /dev/null +++ b/contracts/peer-to-peer/interfaces/oracles/IGTOKEN.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IGTOKEN { + /** + * @notice gets amount of underlying token for a given amount of gToken + * @return amount of underlying token + */ + function shareToAssetsPrice() external view returns (uint256); +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol new file mode 100644 index 00000000..72501906 --- /dev/null +++ b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +//import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; +import {ChainlinkArbitrumSequencerUSD} from "../chainlink/ChainlinkArbitrumSequencerUSD.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "./utils/LogExpMath.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +contract MysoOracle is ChainlinkArbitrumSequencerUSD, Ownable { + struct PriceParams { + // maxPrice is in 8 decimals for chainlink consistency + uint96 maxPrice; + // k is in 18 decimals + // e.g. 8e17 is 0.8 in decimal + uint96 k; + // a and b are in terms of 1000 + // e.g. 1770 is 1.77 in decimal + uint32 a; + uint32 b; + } + // solhint-disable var-name-mixedcase + //address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address + address internal constant MYSO = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address internal constant GDAI = 0xd85E038593d7A098614721EaE955EC2022B9B91B; + address internal constant GUSDC = + 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; + address internal constant GETH = 0x5977A9682D7AF81D347CFc338c61692163a2784C; + address internal constant GLP = 0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258; + address internal constant ETH_USD_CHAINLINK = + 0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612; + address internal constant DAI_ETH_CHAINLINK = + 0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB; + address internal constant USDC_USD_CHAINLINK = + 0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3; + address internal constant GLP_MANAGER = + 0x3963FfC9dff443c2A94f21b129D429891E32ec18; + + uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; + + address public mysoTokenManager; + + PriceParams public mysoPriceParams; + + event MysoTokenManagerUpdated(address newMysoTokenManager); + + error NoMyso(); + + /** + * @dev constructor for MysoOracle + * @param _tokenAddrs array of token addresses + * @param _oracleAddrs array of oracle addresses + * @param _owner owner of the contract + * @param _mysoTokenManager address of myso token manager contract + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + constructor( + address[] memory _tokenAddrs, + address[] memory _oracleAddrs, + address _owner, + address _mysoTokenManager, + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) ChainlinkArbitrumSequencerUSD(_tokenAddrs, _oracleAddrs) Ownable() { + mysoTokenManager = _mysoTokenManager; + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + _transferOwnership(_owner); + } + + /** + * @dev updates myso token manager contract address + * @param _newMysoTokenManager new myso token manager contract address + */ + + function setMysoTokenManager( + address _newMysoTokenManager + ) external onlyOwner { + mysoTokenManager = _newMysoTokenManager; + emit MysoTokenManagerUpdated(_newMysoTokenManager); + } + + /** + * @dev updates myso price params + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + function setMysoPriceParams( + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) external onlyOwner { + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + } + + function getPrice( + address collToken, + address loanToken + ) external view override returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = (loanToken == MYSO) + ? 18 + : IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = + (priceOfCollToken * 10 ** loanTokenDecimals) / + priceOfLoanToken; + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + override + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + // must have at least one token is MYSO to use this oracle + if (collToken != MYSO && loanToken != MYSO) { + revert NoMyso(); + } + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + + function _getPriceOfToken( + address token + ) internal view virtual override returns (uint256 tokenPriceRaw) { + if (token == MYSO) { + tokenPriceRaw = _getMysoPriceInUsd(); + } else if (token == GDAI) { + tokenPriceRaw = _getGTOKENPriceInUsd(GDAI, DAI_ETH_CHAINLINK); + } else if (token == GUSDC) { + tokenPriceRaw = _getGTOKENPriceInUsd(GUSDC, USDC_USD_CHAINLINK); + } else if (token == GETH) { + tokenPriceRaw = _getGTOKENPriceInUsd(GETH, ETH_USD_CHAINLINK); + } else if (token == GLP) { + tokenPriceRaw = IGLPManager(GLP_MANAGER).getPrice(true) / 1e22; + } else { + tokenPriceRaw = super._getPriceOfToken(token); + } + } + + function _getMysoPriceInUsd() + internal + view + returns (uint256 mysoPriceInUsd) + { + uint256 _totalMysoLoanAmount = IMysoTokenManager(mysoTokenManager) + .totalMysoLoanAmount(); + PriceParams memory params = mysoPriceParams; + uint256 maxPrice = uint256(params.maxPrice); + uint256 k = uint256(params.k); + uint256 a = uint256(params.a); + uint256 b = uint256(params.b); + uint256 numerator = k * b; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(_totalMysoLoanAmount, a, 1000000000)) + ) + ) + (2 * b - 1000) * 1e15; + mysoPriceInUsd = maxPrice - Math.mulDiv(numerator, 1e5, denominator); + } + + function _getGTOKENPriceInUsd( + address token, + address chainlinkOracle + ) internal view returns (uint256 gTokenPriceRaw) { + uint256 assetsPerGtoken = IGTOKEN(token).shareToAssetsPrice(); + uint256 assetPriceInUsd = _checkAndReturnLatestRoundData( + chainlinkOracle + ); + uint256 tokenPriceInUsd = _getPriceOfToken(token); + gTokenPriceRaw = Math.mulDiv( + tokenPriceInUsd * assetsPerGtoken, + 1e8, + assetPriceInUsd * 1e18 + ); + } +} diff --git a/contracts/test/TestnetTokenManagerArbitrumOracle.sol b/contracts/test/TestnetTokenManagerArbitrumOracle.sol new file mode 100644 index 00000000..586286dd --- /dev/null +++ b/contracts/test/TestnetTokenManagerArbitrumOracle.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; +import {DataTypesPeerToPool} from "../peer-to-pool/DataTypesPeerToPool.sol"; +import {Errors} from "../Errors.sol"; +import {IMysoTokenManager} from "../interfaces/IMysoTokenManager.sol"; +import {ILenderVaultImpl} from "../peer-to-peer/interfaces/ILenderVaultImpl.sol"; + +contract TestnetTokenManagerArbitrumOracle is Ownable2Step, IMysoTokenManager { + using SafeERC20 for IERC20; + struct RewardInfo { + uint128 collThreshold; + uint128 mysoTokenMultiplier; + } + address internal constant MYSO_TOKEN = + 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; // Dai testnet stand-in arbitrum test + mapping(address => RewardInfo) public rewardInfos; + bool public mysoRewardsActive; + uint256 public totalMysoLoanAmount; + uint256 public minMysoStakingRequirement; + address public mysoIOOVault; + + event RewardInfoSet( + address indexed collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier + ); + event MysoRewardsToggled(bool active); + event MinMysoStakingRequirementSet(uint256 minStakingRequirement); + event IOOVaultSet(address indexed mysoIOOVault); + + // TODO: mapping oracleAddr -> vaultAddr -> tokenAddr -> loanAmount + flag for being turned tracked + // This will allow for other IOOs to use a custom oracle with auto-updating price for loan amount if desired + + constructor() { + minMysoStakingRequirement = 10_000 * 1e18; + _transferOwnership(msg.sender); + } + + function processP2PBorrow( + uint128[2] memory currProtocolFeeParams, + DataTypesPeerToPeer.BorrowTransferInstructions + calldata /*borrowInstructions*/, + DataTypesPeerToPeer.Loan calldata loan, + address lenderVault + ) external returns (uint128[2] memory applicableProtocolFeeParams) { + applicableProtocolFeeParams = currProtocolFeeParams; + if (loan.loanToken == MYSO_TOKEN && lenderVault == mysoIOOVault) { + totalMysoLoanAmount += loan.initLoanAmount; + } + } + + // solhint-disable no-empty-blocks + function processP2PCreateVault( + uint256 /*numRegisteredVaults*/, + address /*vaultCreator*/, + address /*newLenderVaultAddr*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PCreateWrappedTokenForERC721s( + address /*tokenCreator*/, + DataTypesPeerToPeer.WrappedERC721TokenInfo[] + calldata /*tokensToBeWrapped*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PCreateWrappedTokenForERC20s( + address /*tokenCreator*/, + DataTypesPeerToPeer.WrappedERC20TokenInfo[] + calldata /*tokensToBeWrapped*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolDeposit( + address /*fundingPool*/, + address /*depositor*/, + uint256 /*depositAmount*/, + uint256 /*depositLockupDuration*/, + uint256 /*transferFee*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolSubscribe( + address /*fundingPool*/, + address /*subscriber*/, + address /*loanProposal*/, + uint256 /*subscriptionAmount*/, + uint256 /*subscriptionLockupDuration*/, + uint256 /*totalSubscriptions*/, + DataTypesPeerToPool.LoanTerms calldata /*loanTerms*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolLoanFinalization( + address /*loanProposal*/, + address /*fundingPool*/, + address /*arranger*/, + address /*borrower*/, + uint256 /*grossLoanAmount*/, + bytes calldata /*mysoTokenManagerData*/ + ) external {} + + // solhint-disable no-empty-blocks + function processP2PoolCreateLoanProposal( + address /*fundingPool*/, + address /*proposalCreator*/, + address /*collToken*/, + uint256 /*arrangerFee*/, + uint256 /*numLoanProposals*/ + ) external {} + + function withdraw(address token, address to, uint256 amount) external { + _checkOwner(); + SafeERC20.safeTransfer(IERC20(token), to, amount); + } + + function setRewardInfo( + address collToken, + uint128 collThreshold, + uint128 mysoTokenMultiplier + ) external { + _checkOwner(); + RewardInfo storage rewardInfo = rewardInfos[collToken]; + rewardInfo.collThreshold = collThreshold; + rewardInfo.mysoTokenMultiplier = mysoTokenMultiplier; + emit RewardInfoSet(collToken, collThreshold, mysoTokenMultiplier); + } + + function toggleMysoRewards() external { + _checkOwner(); + mysoRewardsActive = !mysoRewardsActive; + emit MysoRewardsToggled(mysoRewardsActive); + } + + function setMinMysoStakingRequirement( + uint256 _minMysoStakingRequirement + ) external { + _checkOwner(); + minMysoStakingRequirement = _minMysoStakingRequirement; + emit MinMysoStakingRequirementSet(_minMysoStakingRequirement); + } + + function setIOOVault(address _mysoIOOVault) external { + _checkOwner(); + mysoIOOVault = _mysoIOOVault; + emit IOOVaultSet(_mysoIOOVault); + } + + function transferOwnership(address _newOwnerProposal) public override { + _checkOwner(); + if ( + _newOwnerProposal == address(0) || + _newOwnerProposal == address(this) || + _newOwnerProposal == pendingOwner() || + _newOwnerProposal == owner() + ) { + revert Errors.InvalidNewOwnerProposal(); + } + super._transferOwnership(_newOwnerProposal); + } +} From 2561f3d8b130bb7330023c04182059ed71fa327c Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Sun, 21 Apr 2024 22:58:04 -0400 Subject: [PATCH 36/42] add missing interfaces --- contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol index 72501906..5e49dc5e 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol @@ -9,6 +9,8 @@ import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol" import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {LogExpMath} from "./utils/LogExpMath.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IGLPManager} from "../../interfaces/oracles/IGLPManager.sol"; +import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces From 817fc967884b15b7743863dc793ad9ce5a647688 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Mon, 22 Apr 2024 17:03:43 -0400 Subject: [PATCH 37/42] added arbitrum oracle tests --- .../oracles/custom/MysoArbitrumUsdOracle.sol | 9 +- hardhat.config.ts | 11 + .../arbitrum-myso-oracle-forked-tests.ts | 488 ++++++++++++++++++ 3 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 test/peer-to-peer/arbitrum-myso-oracle-forked-tests.ts diff --git a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol index 5e49dc5e..0e65c198 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol @@ -15,7 +15,7 @@ import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces */ -contract MysoOracle is ChainlinkArbitrumSequencerUSD, Ownable { +contract MysoArbitrumUsdOracle is ChainlinkArbitrumSequencerUSD, Ownable { struct PriceParams { // maxPrice is in 8 decimals for chainlink consistency uint96 maxPrice; @@ -190,11 +190,6 @@ contract MysoOracle is ChainlinkArbitrumSequencerUSD, Ownable { uint256 assetPriceInUsd = _checkAndReturnLatestRoundData( chainlinkOracle ); - uint256 tokenPriceInUsd = _getPriceOfToken(token); - gTokenPriceRaw = Math.mulDiv( - tokenPriceInUsd * assetsPerGtoken, - 1e8, - assetPriceInUsd * 1e18 - ); + gTokenPriceRaw = Math.mulDiv(assetsPerGtoken, assetPriceInUsd, 1e18); } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 2a3caa70..76624c30 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -39,6 +39,17 @@ export const getMysoOracleMainnetForkingConfig = () => { return { chainId: chainId, url: url, blockNumber: blockNumber } } +export const getMysoOracleArbitrumForkingConfig = () => { + const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY + if (ALCHEMY_API_KEY === undefined) { + throw new Error('Invalid hardhat.config.ts! Need to set `ALCHEMY_API_KEY`!') + } + const chainId = 42161 + const url = `https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}` + const blockNumber = 196000000 // 2024-03-31 (4AM UTC) + return { chainId: chainId, url: url, blockNumber: blockNumber } +} + export const getArbitrumForkingConfig = () => { const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY if (ALCHEMY_API_KEY === undefined) { diff --git a/test/peer-to-peer/arbitrum-myso-oracle-forked-tests.ts b/test/peer-to-peer/arbitrum-myso-oracle-forked-tests.ts new file mode 100644 index 00000000..4a970cdd --- /dev/null +++ b/test/peer-to-peer/arbitrum-myso-oracle-forked-tests.ts @@ -0,0 +1,488 @@ +import { expect } from 'chai' +import { ethers } from 'hardhat' +import { HARDHAT_CHAIN_ID_AND_FORKING_CONFIG, getMysoOracleArbitrumForkingConfig } from '../../hardhat.config' + +// test config constants & vars +let snapshotId: String // use snapshot id to reset state before each test + +// constants +const hre = require('hardhat') +const BASE = ethers.BigNumber.from(10).pow(18) +const ONE_USDC = ethers.BigNumber.from(10).pow(6) +const ONE_WETH = ethers.BigNumber.from(10).pow(18) +const ONE_MYSO = ethers.BigNumber.from(10).pow(18) +const MAX_UINT128 = ethers.BigNumber.from(2).pow(128).sub(1) +const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) +const ONE_HOUR = ethers.BigNumber.from(60 * 60) +const ONE_DAY = ethers.BigNumber.from(60 * 60 * 24) +const ZERO_ADDR = '0x0000000000000000000000000000000000000000' +const ZERO_BYTES32 = ethers.utils.formatBytes32String('') + +describe('Peer-to-Peer: Myso Recent Forked Arbitrum Oracle Tests', function () { + before(async () => { + console.log('Note: Running mainnet tests with the following forking config:') + console.log(HARDHAT_CHAIN_ID_AND_FORKING_CONFIG) + if (HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId !== 1) { + console.warn('Invalid hardhat forking config! Expected `HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId` to be 1!') + + console.warn('Assuming that current test run is using `npx hardhat coverage`!') + + console.warn('Re-importing mainnet forking config from `hardhat.config.ts`...') + const mainnetForkingConfig = getMysoOracleArbitrumForkingConfig() + + console.warn('Overwriting chainId to hardhat default `31337` to make off-chain signing consistent...') + HARDHAT_CHAIN_ID_AND_FORKING_CONFIG.chainId = 31337 + + console.log('block number: ', mainnetForkingConfig.blockNumber) + + console.warn('Trying to manually switch network to forked mainnet for this test file...') + await hre.network.provider.request({ + method: 'hardhat_reset', + params: [ + { + forking: { + jsonRpcUrl: mainnetForkingConfig.url, + blockNumber: mainnetForkingConfig.blockNumber + } + } + ] + }) + } + }) + + beforeEach(async () => { + snapshotId = await hre.network.provider.send('evm_snapshot') + }) + + afterEach(async () => { + await hre.network.provider.send('evm_revert', [snapshotId]) + }) + + async function setupTest() { + const [lender, signer, borrower, team, whitelistAuthority, someUser] = await ethers.getSigners() + /* ************************************ */ + /* DEPLOYMENT OF SYSTEM CONTRACTS START */ + /* ************************************ */ + // deploy address registry + const AddressRegistry = await ethers.getContractFactory('AddressRegistry') + const addressRegistry = await AddressRegistry.connect(team).deploy() + await addressRegistry.deployed() + + // deploy borrower gate way + const BorrowerGateway = await ethers.getContractFactory('BorrowerGateway') + const borrowerGateway = await BorrowerGateway.connect(team).deploy(addressRegistry.address) + await borrowerGateway.deployed() + + // deploy quote handler + const QuoteHandler = await ethers.getContractFactory('QuoteHandler') + const quoteHandler = await QuoteHandler.connect(team).deploy(addressRegistry.address) + await quoteHandler.deployed() + + // deploy lender vault implementation + const LenderVaultImplementation = await ethers.getContractFactory('LenderVaultImpl') + const lenderVaultImplementation = await LenderVaultImplementation.connect(team).deploy() + await lenderVaultImplementation.deployed() + + // deploy LenderVaultFactory + const LenderVaultFactory = await ethers.getContractFactory('LenderVaultFactory') + const lenderVaultFactory = await LenderVaultFactory.connect(team).deploy( + addressRegistry.address, + lenderVaultImplementation.address + ) + await lenderVaultFactory.deployed() + + // initialize address registry + await addressRegistry.connect(team).initialize(lenderVaultFactory.address, borrowerGateway.address, quoteHandler.address) + + /* ********************************** */ + /* DEPLOYMENT OF SYSTEM CONTRACTS END */ + /* ********************************** */ + + // create a vault + await lenderVaultFactory.connect(lender).createVault(ZERO_BYTES32) + const lenderVaultAddrs = await addressRegistry.registeredVaults() + const lenderVaultAddr = lenderVaultAddrs[0] + const lenderVault = await LenderVaultImplementation.attach(lenderVaultAddr) + + // add testnet token manager + const TestnetTokenManager = await ethers.getContractFactory('TestnetTokenManagerArbitrumOracle') + const testnetTokenManager = await TestnetTokenManager.connect(team).deploy() + await testnetTokenManager.connect(team).deployed() + await addressRegistry.connect(team).setWhitelistState([testnetTokenManager.address], 9) + + const DAI_ADDRESS = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1' + const DAI_HOLDER = '0xd85e038593d7a098614721eae955ec2022b9b91b' + const dai = await ethers.getContractAt('IERC20', DAI_ADDRESS) + await ethers.provider.send('hardhat_setBalance', [DAI_HOLDER, '0x56BC75E2D63100000']) + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [DAI_HOLDER] + }) + + const daiHolder = await ethers.getSigner(DAI_HOLDER) + + await dai.connect(daiHolder).transfer(team.address, '15000000000000000000000000') + + const owner = await testnetTokenManager.owner() + + await testnetTokenManager.connect(team).setIOOVault(lenderVaultAddr) + + // prepare WETH balance + const WETH_ADDRESS = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' + const weth = await ethers.getContractAt('IWETH', WETH_ADDRESS) + await ethers.provider.send('hardhat_setBalance', [borrower.address, '0x204FCE5E3E25026110000000']) + await weth.connect(borrower).deposit({ value: ONE_WETH.mul(1000) }) + + return { + addressRegistry, + borrowerGateway, + quoteHandler, + lenderVaultImplementation, + lender, + signer, + borrower, + team, + whitelistAuthority, + weth, + dai, + lenderVault, + lenderVaultFactory, + lenderVaultAddr, + someUser, + testnetTokenManager + } + } + + describe('Myso Arbitrum Oracle Testing', function () { + it('Should set up myso arbitrum IOO price correctly', async function () { + const { + addressRegistry, + borrowerGateway, + quoteHandler, + lender, + borrower, + team, + weth, + dai, + lenderVault, + lenderVaultAddr, + testnetTokenManager + } = await setupTest() + + //const myso = '0x00000000000000000000000000000000DeaDBeef' + const myso = dai.address + const usdc = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' + const usdt = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' + const usdcToUsdChainlinkAddr = '0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3' + const usdtToUsdChainlinkAddr = '0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7' + const wethToUsdChainlinkAddr = '0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612' + const gdai = '0xd85E038593d7A098614721EaE955EC2022B9B91B' + const gusdc = '0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0' + const geth = '0x5977A9682D7AF81D347CFc338c61692163a2784C' + const glp = '0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258' + + // deploy myso oracle + const MysoOracle = await ethers.getContractFactory('MysoArbitrumUsdOracle') + + const mysoOracle = await MysoOracle.connect(lender).deploy( + [usdc, usdt, weth.address], + [usdcToUsdChainlinkAddr, usdtToUsdChainlinkAddr, wethToUsdChainlinkAddr], + team.address, + testnetTokenManager.address, + 55000000, + ethers.BigNumber.from('660000000000000000'), + 1650, + 1000 + ) + await mysoOracle.deployed() + + const mysoPriceData = await mysoOracle.mysoPriceParams() + const mysoOracleOwner = await mysoOracle.owner() + + expect(mysoOracleOwner).to.equal(team.address) + + await expect(mysoOracle.getPrice(weth.address, usdc)).to.be.revertedWithCustomError(mysoOracle, 'NoMyso') + + const wethCollMysoLoanPrice = await mysoOracle.getPrice(weth.address, myso) + const usdcCollMysoLoanPrice = await mysoOracle.getPrice(usdc, myso) + const usdtCollMysoLoanPrice = await mysoOracle.getPrice(usdt, myso) + const glpCollMysoLoanPrice = await mysoOracle.getPrice(glp, myso) + const gdaiCollMysoLoanPrice = await mysoOracle.getPrice(gdai, myso) + const gusdcCollMysoLoanPrice = await mysoOracle.getPrice(gusdc, myso) + const gethCollMysoLoanPrice = await mysoOracle.getPrice(geth, myso) + + const mysoCollWethLoanPrice = await mysoOracle.getPrice(myso, weth.address) + const mysoCollUsdcLoanPrice = await mysoOracle.getPrice(myso, usdc) + const mysoCollUsdtLoanPrice = await mysoOracle.getPrice(myso, usdt) + const mysoCollGdaiLoanPrice = await mysoOracle.getPrice(myso, gdai) + const mysoCollGusdcLoanPrice = await mysoOracle.getPrice(myso, gusdc) + const mysoCollGlpLoanPrice = await mysoOracle.getPrice(myso, glp) + const mysoCollGethLoanPrice = await mysoOracle.getPrice(myso, geth) + + //toggle to show logs + const showLogs = true + if (showLogs) { + console.log( + 'wethCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(wethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'gdaiCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(gdaiCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'gethCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(gethCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'usdcCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'gusdcCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(gusdcCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log( + 'glpCollMysoLoanPrice', + Math.round(1000000 * Number(ethers.utils.formatUnits(glpCollMysoLoanPrice, 18).slice(0, 8))) / 1000000 + ) + console.log(ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(usdtCollMysoLoanPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollWethLoanPrice, 18)) + console.log(ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) + console.log(ethers.utils.formatUnits(mysoCollUsdtLoanPrice, 6)) + console.log('gdai coll myso loan', ethers.utils.formatUnits(gdaiCollMysoLoanPrice, 18)) + console.log('myso coll glp loan', ethers.utils.formatUnits(mysoCollGlpLoanPrice, 18)) + console.log('myso coll gUSDC loan', ethers.utils.formatUnits(mysoCollGusdcLoanPrice, 6)) + } + + await mysoOracle.connect(team).setMysoPriceParams(70000000, ethers.BigNumber.from('800000000000000000'), 1770, 1000) + const newMysoPriceData = await mysoOracle.mysoPriceParams() + expect(newMysoPriceData[0]).to.equal(70000000) + expect(newMysoPriceData[1]).to.equal(ethers.BigNumber.from('800000000000000000')) + expect(newMysoPriceData[2]).to.equal(1770) + expect(newMysoPriceData[3]).to.equal(1000) + + await mysoOracle.connect(team).setMysoPriceParams(55000000, ethers.BigNumber.from('660000000000000000'), 1650, 1000) + + const initialTotalMysoLoanAmount = await testnetTokenManager.totalMysoLoanAmount() + expect(initialTotalMysoLoanAmount).to.equal(0) + + await dai.connect(team).transfer(lenderVault.address, ONE_MYSO.mul(15000000)) + + await addressRegistry.connect(team).setWhitelistState([mysoOracle.address], 2) + + // lenderVault owner gives quote + const blocknum = await ethers.provider.getBlockNumber() + const timestamp = (await ethers.provider.getBlock(blocknum)).timestamp + let quoteTuples = [ + { + loanPerCollUnitOrLtv: BASE, + interestRatePctInBase: 0, + upfrontFeePctInBase: 0, + tenor: ONE_DAY.mul(180) + } + ] + let onChainQuote = { + generalQuoteInfo: { + collToken: weth.address, + loanToken: myso, + oracleAddr: mysoOracle.address, + minLoan: ONE_MYSO.mul(1000), + maxLoan: MAX_UINT256, + validUntil: timestamp + 600, + earliestRepayTenor: 0, + borrowerCompartmentImplementation: ZERO_ADDR, + isSingleUse: false, + whitelistAddr: ZERO_ADDR, + isWhitelistAddrSingleBorrower: false + }, + quoteTuples: quoteTuples, + salt: ZERO_BYTES32 + } + await addressRegistry.connect(team).setWhitelistState([weth.address, myso], 1) + + await expect(quoteHandler.connect(lender).addOnChainQuote(lenderVault.address, onChainQuote)).to.emit( + quoteHandler, + 'OnChainQuoteAdded' + ) + + // borrower approves borrower gateway + await weth.connect(borrower).approve(borrowerGateway.address, MAX_UINT256) + + // borrow with on chain quote + const collSendAmount = ONE_WETH.mul(50) + const expectedProtocolAndVaultTransferFee = 0 + const expectedCompartmentTransferFee = 0 + const quoteTupleIdx = 0 + const callbackAddr = ZERO_ADDR + const callbackData = ZERO_BYTES32 + + const borrowInstructions = { + collSendAmount, + expectedProtocolAndVaultTransferFee, + expectedCompartmentTransferFee, + deadline: MAX_UINT256, + minLoanAmount: 0, + callbackAddr, + callbackData, + mysoTokenManagerData: ZERO_BYTES32 + } + + const borrowWithOnChainQuoteTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, borrowInstructions, onChainQuote, quoteTupleIdx) + + const borrowWithOnChainQuoteReceipt = await borrowWithOnChainQuoteTransaction.wait() + + const borrowEvent = borrowWithOnChainQuoteReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountFirst = borrowEvent?.args?.loan?.['initRepayAmount'] + const initCollAmountFirst = borrowEvent?.args?.loan?.['initCollAmount'] + + const mysoLoanAmountFirstBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountFirst).to.equal(mysoLoanAmountFirstBorrow) + expect(initCollAmountFirst).to.equal(collSendAmount) + + console.log('mysoLoanAmountFirstBorrow', ethers.utils.formatUnits(mysoLoanAmountFirstBorrow, 18)) + + const wethCollMysoLoanPostFirstBorrowPrice = await mysoOracle.getPrice(weth.address, myso) + const mysoCollUsdcLoanPostFirstBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const usdcCollMysoLoanPostFirstBorrowPrice = await mysoOracle.getPrice(usdc, myso) + expect(wethCollMysoLoanPostFirstBorrowPrice).to.be.lt(wethCollMysoLoanPrice) + expect(usdcCollMysoLoanPostFirstBorrowPrice).to.be.lt(usdcCollMysoLoanPrice) + if (showLogs) { + console.log( + 'usdcCollMysoLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostFirstBorrowPrice, 18) + ) + console.log('usdcCollMysoLoanPrice', ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) + console.log( + 'mysoCollUsdcLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFirstBorrowPrice, 6) + ) + console.log('mysoCollUsdcLoanPrice', ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) + } + + const secondBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, borrowInstructions, onChainQuote, quoteTupleIdx) + + const secondBorrowReceipt = await secondBorrowTransaction.wait() + + const secondBorrowEvent = secondBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountSecond = secondBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountSecondBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountSecond).to.equal(mysoLoanAmountSecondBorrow.sub(mysoLoanAmountFirstBorrow)) + + console.log('mysoLoanAmountSecondBorrow', ethers.utils.formatUnits(mysoLoanAmountSecondBorrow, 18)) + + const mysoCollUsdcLoanPostSecondBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const usdcCollMysoLoanPostSecondBorrowPrice = await mysoOracle.getPrice(usdc, myso) + if (showLogs) { + console.log( + 'usdcCollMysoLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostSecondBorrowPrice, 18) + ) + console.log( + 'usdcCollMysoLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(usdcCollMysoLoanPostFirstBorrowPrice, 18) + ) + console.log( + 'mysoCollUsdcLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostSecondBorrowPrice, 6) + ) + console.log( + 'mysoCollUsdcLoanPostFirstBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFirstBorrowPrice, 6) + ) + } + + const largeBorrowInstructions = { + ...borrowInstructions, + collSendAmount: ONE_WETH.mul(250) + } + + const thirdBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, largeBorrowInstructions, onChainQuote, quoteTupleIdx) + + const thirdBorrowReceipt = await thirdBorrowTransaction.wait() + + const thirdBorrowEvent = thirdBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountThird = thirdBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountThirdBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountThird).to.equal(mysoLoanAmountThirdBorrow.sub(mysoLoanAmountSecondBorrow)) + + console.log('mysoLoanAmountThirdBorrow', ethers.utils.formatUnits(mysoLoanAmountThirdBorrow, 18)) + + const mysoCollUsdcLoanPostThirdBorrowPrice = await mysoOracle.getPrice(myso, usdc) + + if (showLogs) { + console.log( + 'mysoCollUsdcLoanPostThirdBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostThirdBorrowPrice, 6) + ) + console.log( + 'mysoCollUsdcLoanPostSecondBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostSecondBorrowPrice, 6) + ) + } + + const fourthBorrowTransaction = await borrowerGateway + .connect(borrower) + .borrowWithOnChainQuote(lenderVault.address, largeBorrowInstructions, onChainQuote, quoteTupleIdx) + + const fourthBorrowReceipt = await fourthBorrowTransaction.wait() + + const fourthBorrowEvent = fourthBorrowReceipt.events?.find(x => { + return x.event === 'Borrowed' + }) + + const repayAmountFourth = fourthBorrowEvent?.args?.loan?.['initRepayAmount'] + + const mysoLoanAmountFourthBorrow = await testnetTokenManager.totalMysoLoanAmount() + + expect(repayAmountFourth).to.equal(mysoLoanAmountFourthBorrow.sub(mysoLoanAmountThirdBorrow)) + + console.log('mysoLoanAmountFourthBorrow', ethers.utils.formatUnits(mysoLoanAmountFourthBorrow, 18)) + + const mysoCollUsdcLoanPostFourthBorrowPrice = await mysoOracle.getPrice(myso, usdc) + const wethCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(weth.address, myso) + const glpCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(glp, myso) + const gethCollMysoLoanPostFourthBorrowPrice = await mysoOracle.getPrice(geth, myso) + + if (showLogs) { + console.log( + 'mysoCollUsdcLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(mysoCollUsdcLoanPostFourthBorrowPrice, 6) + ) + console.log( + 'wethCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(wethCollMysoLoanPostFourthBorrowPrice, 18) + ) + console.log( + 'glpCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(glpCollMysoLoanPostFourthBorrowPrice, 18) + ) + console.log( + 'gethCollMysoLoanPostFourthBorrowPrice', + ethers.utils.formatUnits(gethCollMysoLoanPostFourthBorrowPrice, 18) + ) + } + }) + }) +}) From 4e1b14649256e4ff30e3b6e7bdc710bf67e298fa Mon Sep 17 00:00:00 2001 From: asardon Date: Thu, 2 May 2024 23:28:51 +0200 Subject: [PATCH 38/42] add deployment scripts, bnb myso oracle, arb token manager with p2pool rewards --- .../oracles/custom/MysoArbitrumUsdOracle.sol | 4 +- .../oracles/custom/MysoBnbUsdOracle.sol | 163 ++++++++++++++++++ .../oracles/custom/MysoOracle.sol | 3 +- contracts/tokenManager/MysoTokenManager.sol | 14 +- .../tokenManager/MysoTokenManagerArbitrum.sol | 123 +++++++++++++ hardhat.config.ts | 21 ++- .../dao/deployMysoArbitrumOracle.ts | 62 +++++++ .../dao/deployMysoArbitrumTokenManager.ts | 49 ++++++ .../peer-to-peer/dao/deployMysoBnbOracle.ts | 59 +++++++ .../dao/deployMysoBnbTokenManager.ts | 49 ++++++ .../dao/deployMysoMainnetOracle.ts | 62 +++++++ .../dao/deployMysoMainnetTokenManager.ts | 49 ++++++ .../dao/deployMysoMantleTokenManager.ts | 49 ++++++ .../dao/manageAddressRegistry.json | 30 +++- .../dao/mysoArbitrumOracleArgs.js | 18 ++ .../dao/mysoArbitrumTokenManagerArgs.js | 8 + .../dao/mysoBnbTokenManagerArgs.js | 8 + .../dao/mysoBnbTokenOracleArgs.js | 16 ++ .../peer-to-peer/dao/mysoMainnetOracleArgs.js | 18 ++ .../dao/mysoMainnetTokenManagerArgs.js | 8 + scripts/peer-to-pool/createFundingPool.ts | 27 +++ scripts/peer-to-pool/deploy.ts | 35 ++-- 22 files changed, 847 insertions(+), 28 deletions(-) create mode 100644 contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol create mode 100644 contracts/tokenManager/MysoTokenManagerArbitrum.sol create mode 100644 scripts/peer-to-peer/dao/deployMysoArbitrumOracle.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoArbitrumTokenManager.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoBnbOracle.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoBnbTokenManager.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoMainnetOracle.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoMainnetTokenManager.ts create mode 100644 scripts/peer-to-peer/dao/deployMysoMantleTokenManager.ts create mode 100644 scripts/peer-to-peer/dao/mysoArbitrumOracleArgs.js create mode 100644 scripts/peer-to-peer/dao/mysoArbitrumTokenManagerArgs.js create mode 100644 scripts/peer-to-peer/dao/mysoBnbTokenManagerArgs.js create mode 100644 scripts/peer-to-peer/dao/mysoBnbTokenOracleArgs.js create mode 100644 scripts/peer-to-peer/dao/mysoMainnetOracleArgs.js create mode 100644 scripts/peer-to-peer/dao/mysoMainnetTokenManagerArgs.js create mode 100644 scripts/peer-to-pool/createFundingPool.ts diff --git a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol index 0e65c198..e3c92a82 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoArbitrumUsdOracle.sol @@ -28,8 +28,8 @@ contract MysoArbitrumUsdOracle is ChainlinkArbitrumSequencerUSD, Ownable { uint32 b; } // solhint-disable var-name-mixedcase - //address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address - address internal constant MYSO = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + + address internal constant MYSO = 0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12; address internal constant GDAI = 0xd85E038593d7A098614721EaE955EC2022B9B91B; address internal constant GUSDC = 0xd3443ee1e91aF28e5FB858Fbd0D72A63bA8046E0; diff --git a/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol new file mode 100644 index 00000000..a1a5e948 --- /dev/null +++ b/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +//import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; +import {ChainlinkArbitrumSequencerUSD} from "../chainlink/ChainlinkArbitrumSequencerUSD.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "./utils/LogExpMath.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IGLPManager} from "../../interfaces/oracles/IGLPManager.sol"; +import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +contract MysoBnbUsdOracle is ChainlinkArbitrumSequencerUSD, Ownable { + struct PriceParams { + // maxPrice is in 8 decimals for chainlink consistency + uint96 maxPrice; + // k is in 18 decimals + // e.g. 8e17 is 0.8 in decimal + uint96 k; + // a and b are in terms of 1000 + // e.g. 1770 is 1.77 in decimal + uint32 a; + uint32 b; + } + // solhint-disable var-name-mixedcase + + uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; + + address internal constant MYSO = 0x85be1fAB94E9AB109ea339E164689281FAd3f0dF; + + address public mysoTokenManager; + + PriceParams public mysoPriceParams; + + event MysoTokenManagerUpdated(address newMysoTokenManager); + + error NoMyso(); + + /** + * @dev constructor for MysoOracle + * @param _tokenAddrs array of token addresses + * @param _oracleAddrs array of oracle addresses + * @param _owner owner of the contract + * @param _mysoTokenManager address of myso token manager contract + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + constructor( + address[] memory _tokenAddrs, + address[] memory _oracleAddrs, + address _owner, + address _mysoTokenManager, + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) ChainlinkArbitrumSequencerUSD(_tokenAddrs, _oracleAddrs) Ownable() { + mysoTokenManager = _mysoTokenManager; + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + _transferOwnership(_owner); + } + + /** + * @dev updates myso token manager contract address + * @param _newMysoTokenManager new myso token manager contract address + */ + + function setMysoTokenManager( + address _newMysoTokenManager + ) external onlyOwner { + mysoTokenManager = _newMysoTokenManager; + emit MysoTokenManagerUpdated(_newMysoTokenManager); + } + + /** + * @dev updates myso price params + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + function setMysoPriceParams( + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) external onlyOwner { + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + } + + function getPrice( + address collToken, + address loanToken + ) external view override returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = (loanToken == MYSO) + ? 18 + : IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = + (priceOfCollToken * 10 ** loanTokenDecimals) / + priceOfLoanToken; + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + override + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + // must have at least one token is MYSO to use this oracle + if (collToken != MYSO && loanToken != MYSO) { + revert NoMyso(); + } + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + + function _getPriceOfToken( + address token + ) internal view virtual override returns (uint256 tokenPriceRaw) { + if (token == MYSO) { + tokenPriceRaw = _getMysoPriceInUsd(); + } else { + tokenPriceRaw = super._getPriceOfToken(token); + } + } + + function _getMysoPriceInUsd() + internal + view + returns (uint256 mysoPriceInUsd) + { + uint256 _totalMysoLoanAmount = IMysoTokenManager(mysoTokenManager) + .totalMysoLoanAmount(); + PriceParams memory params = mysoPriceParams; + uint256 maxPrice = uint256(params.maxPrice); + uint256 k = uint256(params.k); + uint256 a = uint256(params.a); + uint256 b = uint256(params.b); + uint256 numerator = k * b; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(_totalMysoLoanAmount, a, 1000000000)) + ) + ) + (2 * b - 1000) * 1e15; + mysoPriceInUsd = maxPrice - Math.mulDiv(numerator, 1e5, denominator); + } +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol index fdcf38c1..f5febe3f 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoOracle.sol @@ -30,8 +30,7 @@ contract MysoOracle is ChainlinkBase, Ownable { uint32 b; } // solhint-disable var-name-mixedcase - //address internal constant MYSO = 0x00000000000000000000000000000000DeaDBeef; // TODO: put in real myso address - address internal constant MYSO = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address internal constant MYSO = 0x5fDe99e121F3aC02e7d6ACb081dB1f89c1e93C17; address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; diff --git a/contracts/tokenManager/MysoTokenManager.sol b/contracts/tokenManager/MysoTokenManager.sol index 5e1a5099..ac8aedbc 100644 --- a/contracts/tokenManager/MysoTokenManager.sol +++ b/contracts/tokenManager/MysoTokenManager.sol @@ -113,7 +113,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { uint256 /*numRegisteredVaults*/, address /*vaultCreator*/, address /*newLenderVaultAddr*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PCreateWrappedTokenForERC721s( @@ -121,7 +121,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { DataTypesPeerToPeer.WrappedERC721TokenInfo[] calldata /*tokensToBeWrapped*/, bytes calldata /*mysoTokenManagerData*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PCreateWrappedTokenForERC20s( @@ -129,7 +129,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { DataTypesPeerToPeer.WrappedERC20TokenInfo[] calldata /*tokensToBeWrapped*/, bytes calldata /*mysoTokenManagerData*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PoolDeposit( @@ -138,7 +138,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { uint256 /*depositAmount*/, uint256 /*depositLockupDuration*/, uint256 /*transferFee*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PoolSubscribe( @@ -149,7 +149,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { uint256 /*subscriptionLockupDuration*/, uint256 /*totalSubscriptions*/, DataTypesPeerToPool.LoanTerms calldata /*loanTerms*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PoolLoanFinalization( @@ -159,7 +159,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address /*borrower*/, uint256 /*grossLoanAmount*/, bytes calldata /*mysoTokenManagerData*/ - ) external {} + ) external virtual {} // solhint-disable no-empty-blocks function processP2PoolCreateLoanProposal( @@ -168,7 +168,7 @@ contract MysoTokenManager is Ownable2Step, IMysoTokenManager { address /*collToken*/, uint256 /*arrangerFee*/, uint256 /*numLoanProposals*/ - ) external {} + ) external virtual {} function withdraw(address token, address to, uint256 amount) external { _checkOwner(); diff --git a/contracts/tokenManager/MysoTokenManagerArbitrum.sol b/contracts/tokenManager/MysoTokenManagerArbitrum.sol new file mode 100644 index 00000000..8e180698 --- /dev/null +++ b/contracts/tokenManager/MysoTokenManagerArbitrum.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.19; + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {MysoTokenManagerWithCaps} from "./MysoTokenManagerWithCaps.sol"; +import {DataTypesPeerToPeer} from "../peer-to-peer/DataTypesPeerToPeer.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MysoTokenManagerArbitrum is MysoTokenManagerWithCaps { + using SafeERC20 for IERC20Metadata; + + struct P2PoolRewardInfo { + address rewardToken; + uint256 rewardMultiplier; + } + mapping(address => mapping(uint256 => mapping(uint256 => P2PoolRewardInfo))) + public p2PoolRewardInfo; + + event P2PoolRewardInfoSet( + address indexed fundingPool, + uint256 depositLockupDuration, + uint256 index, + address rewardToken, + uint256 rewardMultiplier + ); + + error InvalidToAddress(); + error OufOfBounds(); + + constructor( + address _mysoIOOVault, + address _mysoToken, + address _stMysoToken, + uint256 _minMysoWeight, + address _signer, + CollatCapInfo[] memory _collatCaps + ) + MysoTokenManagerWithCaps( + _mysoIOOVault, + _mysoToken, + _stMysoToken, + _minMysoWeight, + _signer, + _collatCaps + ) + {} + + function processP2PoolDeposit( + address fundingPool, + address depositor, + uint256 depositAmount, + uint256 depositLockupDuration, + uint256 /*transferFee*/ + ) external virtual override { + ( + uint256[] memory rewards, + address[] memory tokens + ) = getP2PoolDepositRewards( + fundingPool, + depositAmount, + depositLockupDuration + ); + for (uint256 i = 0; i < 8; i++) { + if (rewards[i] == 0) { + break; + } + IERC20Metadata(tokens[i]).safeTransfer(depositor, rewards[i]); + } + } + + function setP2PoolRewardInfo( + address fundingPool, + uint256 depositLockupDuration, + uint256 index, + address rewardToken, + uint256 rewardMultiplier + ) external onlyOwner { + if (index >= 8) { + revert OufOfBounds(); + } + p2PoolRewardInfo[fundingPool][depositLockupDuration][ + index + ] = P2PoolRewardInfo({ + rewardToken: rewardToken, + rewardMultiplier: rewardMultiplier + }); + emit P2PoolRewardInfoSet( + fundingPool, + depositLockupDuration, + index, + rewardToken, + rewardMultiplier + ); + } + + function getP2PoolDepositRewards( + address fundingPool, + uint256 depositAmount, + uint256 depositLockupDuration + ) public view returns (uint256[] memory, address[] memory) { + uint256[] memory rewards = new uint256[](8); + address[] memory tokens = new address[](8); + + for (uint256 i = 0; i < 8; i++) { + P2PoolRewardInfo memory _p2PoolRewardInfo = p2PoolRewardInfo[ + fundingPool + ][depositLockupDuration][i]; + if ( + _p2PoolRewardInfo.rewardToken == address(0) || + _p2PoolRewardInfo.rewardMultiplier == 0 + ) { + break; + } + rewards[i] = + (_p2PoolRewardInfo.rewardMultiplier * depositAmount) / + 1e18; + tokens[i] = _p2PoolRewardInfo.rewardToken; + } + + return (rewards, tokens); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 76624c30..b64cde37 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -143,7 +143,22 @@ const config: HardhatUserConfig = { mantleMainnet: { chainId: 5000, url: `https://rpc.mantle.xyz/`, //`https://rpc.ankr.com/mantle/${process.env.ANKR_API_KEY}`, - accounts: [`0x${process.env.MANTLE_MAINNET_DEPLOYER_KEY}`] + accounts: [`0x${process.env.MANTLE_MAINNET_DEPLOYER_KEY}`, `0x${process.env.MANTLE_EARLY_ACCESS}`] + }, + mainnet: { + chainId: 1, + url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_MAINNET_API_KEY}`, + accounts: [`0x${process.env.ETHEREUM_MAINNET_DEPLOYER}`, `0x${process.env.ETHEREUM_MAINNET_EARLY_ACCESS}`] + }, + arbitrum: { + chainId: 42161, + url: `https://arb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, + accounts: [`0x${process.env.ARBITRUM_MAINNET_DEPLOYER_KEY}`, `0x${process.env.ETHEREUM_MAINNET_EARLY_ACCESS}`] + }, + bnb: { + chainId: 56, + url: `https://bsc.drpc.org`, //`https://bsc.meowrpc.com`, + accounts: [`0x${process.env.BNB_DEPLOYER_KEY}`, `0x${process.env.BNB_EARLY_ACCESS}`] } }, mocha: { @@ -169,9 +184,7 @@ const config: HardhatUserConfig = { enabled: true }, etherscan: { - apiKey: { - sepolia: process.env.ETHERSCAN_API_KEY - } + apiKey: process.env.ARBISCAN_API_KEY //process.env.ARBISCAN_API_KEY, BSCAN_API_KEY } } diff --git a/scripts/peer-to-peer/dao/deployMysoArbitrumOracle.ts b/scripts/peer-to-peer/dao/deployMysoArbitrumOracle.ts new file mode 100644 index 00000000..91277f0d --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoArbitrumOracle.ts @@ -0,0 +1,62 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const usdc = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" + const usdt = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9" + const dai = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1" + const usdcUsdOracle = "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3" + const usdtUsdOracle = "0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7" + const daiUsdOracle = "0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB" + const tokenAddrs = [usdc, usdt, dai] + const oracleAddrs = [usdcUsdOracle, usdtUsdOracle, daiUsdOracle] + const owner = deployer.address + const mysoTokenManager = "0x5241Aebb80db39b1A54c12b2A7eb96C431F8C9f1" + // for curve params, see: https://www.desmos.com/calculator/fo7fpzfcaq + // NOTE: price is token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const maxPrice = ethers.utils.parseUnits("0.55", 8) + // NOTE: k is in token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const k = ethers.utils.parseUnits("0.66", 18) + const a = ethers.utils.parseUnits("8", 3) + const b = ethers.utils.parseUnits("0.8", 3) + + console.log(tokenAddrs) + console.log(oracleAddrs) + console.log(owner) + console.log(mysoTokenManager) + console.log(maxPrice) + console.log(k) + console.log(a) + console.log(b) + + const MysoArbitrumUsdOracle = await ethers.getContractFactory('MysoArbitrumUsdOracle') + const mysoArbitrumUsdOracle = await MysoArbitrumUsdOracle.connect(deployer).deploy(tokenAddrs, oracleAddrs, owner, mysoTokenManager, maxPrice, k, a, b) + await mysoArbitrumUsdOracle.deployed() + logger.log('mysoArbitrumUsdOracle deployed at:', mysoArbitrumUsdOracle.address) + + // npx hardhat verify --constructor-args .\scripts\peer-to-peer\dao\mysoArbitrumOracleArgs.js --network arbitrum 0x2da3354790f942E3A57c325220F10731a7887946 +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoArbitrumTokenManager.ts b/scripts/peer-to-peer/dao/deployMysoArbitrumTokenManager.ts new file mode 100644 index 00000000..bbbcff75 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoArbitrumTokenManager.ts @@ -0,0 +1,49 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer, earlyAccessSigner] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const mysoIOOVault = "0xC9d073458C4C2737f9A81940FA744E17F9F11eB0" + const mysoToken = "0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12" + const stMysoToken = ethers.constants.AddressZero + const minMysoWeight = ethers.constants.MaxUint256 + const signer = earlyAccessSigner.address + const collatCaps : any = [] + + console.log(mysoIOOVault) + console.log(mysoToken) + console.log(stMysoToken) + console.log(minMysoWeight) + console.log(signer) + console.log(collatCaps) + + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer, collatCaps) + await mysoTokenManagerWithCaps.deployed() + logger.log('mysoTokenManagerWithCaps deployed at:', mysoTokenManagerWithCaps.address) + + // npx hardhat verify --constructor-args .\scripts\peer-to-peer\dao\mysoArbitrumTokenManagerArgs.js --network arbitrum 0x5241Aebb80db39b1A54c12b2A7eb96C431F8C9f1 +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoBnbOracle.ts b/scripts/peer-to-peer/dao/deployMysoBnbOracle.ts new file mode 100644 index 00000000..458be497 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoBnbOracle.ts @@ -0,0 +1,59 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const usdcBinancePegged = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + const usdc = "0x8965349fb649A33a30cbFDa057D8eC2C48AbE2A2" + const usdcUsdOracle = "0x51597f405303C4377E36123cBc172b13269EA163" + const tokenAddrs = [usdcBinancePegged, usdc] + const oracleAddrs = [usdcUsdOracle, usdcUsdOracle] + const owner = deployer.address + const mysoTokenManager = "0x937EE6ddB61fE1d4576b34b7ab598004612A4cD8" + // for curve params, see: https://www.desmos.com/calculator/fo7fpzfcaq + // NOTE: price is token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const maxPrice = ethers.utils.parseUnits("0.55", 8) + // NOTE: k is in token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const k = ethers.utils.parseUnits("0.66", 18) + const a = ethers.utils.parseUnits("8", 3) + const b = ethers.utils.parseUnits("0.8", 3) + + console.log(tokenAddrs) + console.log(oracleAddrs) + console.log(owner) + console.log(mysoTokenManager) + console.log(maxPrice) + console.log(k) + console.log(a) + console.log(b) + + const MysoBnbUsdOracle = await ethers.getContractFactory('MysoBnbUsdOracle') + const mysoBnbUsdOracle = await MysoBnbUsdOracle.connect(deployer).deploy(tokenAddrs, oracleAddrs, owner, mysoTokenManager, maxPrice, k, a, b) + await mysoBnbUsdOracle.deployed() + logger.log('mysoBnbUsdOracle deployed at:', mysoBnbUsdOracle.address) + + // npx hardhat verify --constructor-args .\scripts\peer-to-peer\dao\mysoBnbTokenOracleArgs.js --network bnb 0xf5BfbdDaf0e0CD3551175b6296590BDc6362Fd69 +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoBnbTokenManager.ts b/scripts/peer-to-peer/dao/deployMysoBnbTokenManager.ts new file mode 100644 index 00000000..d1612940 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoBnbTokenManager.ts @@ -0,0 +1,49 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer, earlyAccessSigner] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const mysoIOOVault = "0x6b50BFf92863C1c70EfE0CA65602BAfE47a7577c" + const mysoToken = "0x85be1fab94e9ab109ea339e164689281fad3f0df" + const stMysoToken = ethers.constants.AddressZero + const minMysoWeight = ethers.constants.MaxUint256 + const signer = earlyAccessSigner.address + const collatCaps : any = [] + + console.log(mysoIOOVault) + console.log(mysoToken) + console.log(stMysoToken) + console.log(minMysoWeight) + console.log(signer) + console.log(collatCaps) + + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer, collatCaps) + await mysoTokenManagerWithCaps.deployed() + logger.log('mysoTokenManagerWithCaps deployed at:', mysoTokenManagerWithCaps.address) + + // npx hardhat verify --constructor-args .\scripts\peer-to-peer\dao\mysoBnbTokenManagerArgs.js --network arbitrum 0x937EE6ddB61fE1d4576b34b7ab598004612A4cD8 +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoMainnetOracle.ts b/scripts/peer-to-peer/dao/deployMysoMainnetOracle.ts new file mode 100644 index 00000000..1dbbc1d5 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoMainnetOracle.ts @@ -0,0 +1,62 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + const usdt = "0xdAC17F958D2ee523a2206206994597C13D831ec7" + const dai = "0x6B175474E89094C44Da98b954EedeAC495271d0F" + const usdcEthOracle = "0x986b5E1e1755e3C2440e960477f25201B0a8bbD4" + const usdtEthOracle = "0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46" + const daiEthOracle = "0x773616E4d11A78F511299002da57A0a94577F1f4" + const tokenAddrs = [usdc, usdt, dai] + const oracleAddrs = [usdcEthOracle, usdtEthOracle, daiEthOracle] + const owner = deployer.address + const mysoTokenManager = "0x795F84270B085F449A92E943De38062Aa5A2fBa4" + // for curve params, see: https://www.desmos.com/calculator/cpi1fcpcwz + // NOTE: price is token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const maxPrice = ethers.utils.parseUnits("0.55", 8) + // NOTE: k is in token price, NOT FDV (ie to make consistent with desmos plot shift by 2 decimals) + const k = ethers.utils.parseUnits("0.66", 18) + const a = ethers.utils.parseUnits("4.14", 3) + const b = ethers.utils.parseUnits("0.8", 3) + + console.log(tokenAddrs) + console.log(oracleAddrs) + console.log(owner) + console.log(mysoTokenManager) + console.log(maxPrice) + console.log(k) + console.log(a) + console.log(b) + + const MysoOracle = await ethers.getContractFactory('MysoOracle') + const mysoOracle = await MysoOracle.connect(deployer).deploy(tokenAddrs, oracleAddrs, owner, mysoTokenManager, maxPrice, k, a, b) + await mysoOracle.deployed() + logger.log('mysoOracle deployed at:', mysoOracle.address) + + // npx hardhat verify --constructor-args .\scripts\peer-to-peer\dao\mysoMainnetOracleArgs.js --network mainnet 0xd02cE60A586f74124BB94c9DBE27c151344e7cEa +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoMainnetTokenManager.ts b/scripts/peer-to-peer/dao/deployMysoMainnetTokenManager.ts new file mode 100644 index 00000000..6d57d268 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoMainnetTokenManager.ts @@ -0,0 +1,49 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer, earlyAccessSigner] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const mysoIOOVault = "0xB6307b0f7f85BB5c8DF1569ac1621A78A2Be11A7" + const mysoToken = "0x5fde99e121f3ac02e7d6acb081db1f89c1e93c17" + const stMysoToken = ethers.constants.AddressZero + const minMysoWeight = ethers.constants.MaxUint256 + const signer = earlyAccessSigner.address + const collatCaps : any = [] + + console.log(mysoIOOVault) + console.log(mysoToken) + console.log(stMysoToken) + console.log(minMysoWeight) + console.log(signer) + console.log(collatCaps) + + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer, collatCaps) + await mysoTokenManagerWithCaps.deployed() + logger.log('mysoTokenManagerWithCaps deployed at:', mysoTokenManagerWithCaps.address) + + // npx hardhat verify 0x16B67F0dc94e0fAd995d4dFAB77170d30848c277 "0xcE3d0e78c15C30ecf631ef529581Af3de0478895" "0x0000000000000000000000000000000000000000" "5500000000000000" "66000000000000000000" "1350" "600" --network sepolia +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/deployMysoMantleTokenManager.ts b/scripts/peer-to-peer/dao/deployMysoMantleTokenManager.ts new file mode 100644 index 00000000..ee6e7e86 --- /dev/null +++ b/scripts/peer-to-peer/dao/deployMysoMantleTokenManager.ts @@ -0,0 +1,49 @@ +import { ethers } from 'hardhat' +import { Logger } from '../../helpers/misc' + +const hre = require('hardhat') +const path = require('path') +const scriptName = path.parse(__filename).name +const logger = new Logger(__dirname, scriptName) + +async function main() { + logger.log(`Starting ${scriptName}...`) + logger.log('Loading signer info (check hardhat.config.ts)...') + + const [deployer, earlyAccessSigner] = await ethers.getSigners() + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + + logger.log('Running script with the following deployer:', deployer.address) + logger.log('Deployer ETH balance:', ethers.utils.formatEther(deployerBal.toString())) + logger.log(`Deploying to network '${hardhatNetworkName}' (default provider network name '${network.name}')`) + logger.log(`Configured chain id '${hardhatChainId}' (default provider config chain id '${network.chainId}')`) + + const mysoIOOVault = "0xC9d073458C4C2737f9A81940FA744E17F9F11eB0" + const mysoToken = "0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12" + const stMysoToken = ethers.constants.AddressZero + const minMysoWeight = ethers.constants.MaxUint256 + const signer = earlyAccessSigner.address + const collatCaps : any = [] + + console.log(mysoIOOVault) + console.log(mysoToken) + console.log(stMysoToken) + console.log(minMysoWeight) + console.log(signer) + console.log(collatCaps) + + const MysoTokenManagerWithCaps = await ethers.getContractFactory('MysoTokenManagerWithCaps') + const mysoTokenManagerWithCaps = await MysoTokenManagerWithCaps.connect(deployer).deploy(mysoIOOVault, mysoToken, stMysoToken, minMysoWeight, signer, collatCaps) + await mysoTokenManagerWithCaps.deployed() + logger.log('mysoTokenManagerWithCaps deployed at:', mysoTokenManagerWithCaps.address) + + // npx hardhat verify 0x16B67F0dc94e0fAd995d4dFAB77170d30848c277 "0xcE3d0e78c15C30ecf631ef529581Af3de0478895" "0x0000000000000000000000000000000000000000" "5500000000000000" "66000000000000000000" "1350" "600" --network sepolia +} + +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-peer/dao/manageAddressRegistry.json b/scripts/peer-to-peer/dao/manageAddressRegistry.json index 3875a052..eabe5b52 100644 --- a/scripts/peer-to-peer/dao/manageAddressRegistry.json +++ b/scripts/peer-to-peer/dao/manageAddressRegistry.json @@ -33,8 +33,34 @@ "addressRegistry": "0x196A6649e2A7eb225Ee53BA507552cCc8BcdB07a", "actions": [ { - "type": "transferOwnership", - "newOwnerProposal": "0x8E2Dd4d57dF7E3480ABFe0eACa1c0Ccd41F1E18E" + "type": "setWhitelistState", + "addresses": ["0x5eeFfE74E10765e832df75760c7c7dA7531514F2"], + "state": 2 + } + ] + }, + "arbitrum": { + "addressRegistry": "0x196A6649e2A7eb225Ee53BA507552cCc8BcdB07a", + "actions": [ + { + "type": "setWhitelistState", + "addresses": ["0x5eeFfE74E10765e832df75760c7c7dA7531514F2"], + "state": 0 + }, + { + "type": "setWhitelistState", + "addresses": ["0x2da3354790f942E3A57c325220F10731a7887946"], + "state": 2 + } + ] + }, + "bnb": { + "addressRegistry": "0x5B1d65dCE6ade0c82678Ccf51ABE255266A9BD18", + "actions": [ + { + "type": "setWhitelistState", + "addresses": ["0xf5BfbdDaf0e0CD3551175b6296590BDc6362Fd69"], + "state": 2 } ] } diff --git a/scripts/peer-to-peer/dao/mysoArbitrumOracleArgs.js b/scripts/peer-to-peer/dao/mysoArbitrumOracleArgs.js new file mode 100644 index 00000000..193631e1 --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoArbitrumOracleArgs.js @@ -0,0 +1,18 @@ +module.exports = [ + [ + '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', + '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1' + ], + [ + '0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3', + '0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7', + '0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB' + ], + "0x2b13a4AcFF9934f1c60582Dcd5a7db947E74AdEb", + "0x5241Aebb80db39b1A54c12b2A7eb96C431F8C9f1", + "55000000", + "660000000000000000", + "8000", + "800" +]; \ No newline at end of file diff --git a/scripts/peer-to-peer/dao/mysoArbitrumTokenManagerArgs.js b/scripts/peer-to-peer/dao/mysoArbitrumTokenManagerArgs.js new file mode 100644 index 00000000..4750a69d --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoArbitrumTokenManagerArgs.js @@ -0,0 +1,8 @@ +module.exports = [ + "0xC9d073458C4C2737f9A81940FA744E17F9F11eB0", + "0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12", + "0x0000000000000000000000000000000000000000", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "0x546881EbC10691072ECC0830Dba7E3E7281D9835", + [], + ]; \ No newline at end of file diff --git a/scripts/peer-to-peer/dao/mysoBnbTokenManagerArgs.js b/scripts/peer-to-peer/dao/mysoBnbTokenManagerArgs.js new file mode 100644 index 00000000..8c813c84 --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoBnbTokenManagerArgs.js @@ -0,0 +1,8 @@ +module.exports = [ + "0x6b50BFf92863C1c70EfE0CA65602BAfE47a7577c", + "0x85be1fab94e9ab109ea339e164689281fad3f0df", + "0x0000000000000000000000000000000000000000", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "0x546881EbC10691072ECC0830Dba7E3E7281D9835", + [], + ]; \ No newline at end of file diff --git a/scripts/peer-to-peer/dao/mysoBnbTokenOracleArgs.js b/scripts/peer-to-peer/dao/mysoBnbTokenOracleArgs.js new file mode 100644 index 00000000..811fdef7 --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoBnbTokenOracleArgs.js @@ -0,0 +1,16 @@ +module.exports = [ + [ + '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + '0x8965349fb649A33a30cbFDa057D8eC2C48AbE2A2' + ], + [ + '0x51597f405303C4377E36123cBc172b13269EA163', + '0x51597f405303C4377E36123cBc172b13269EA163' + ], + "0x2b13a4AcFF9934f1c60582Dcd5a7db947E74AdEb", + "0x937EE6ddB61fE1d4576b34b7ab598004612A4cD8", + "55000000", + "660000000000000000", + "8000", + "800" + ]; \ No newline at end of file diff --git a/scripts/peer-to-peer/dao/mysoMainnetOracleArgs.js b/scripts/peer-to-peer/dao/mysoMainnetOracleArgs.js new file mode 100644 index 00000000..a72d4009 --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoMainnetOracleArgs.js @@ -0,0 +1,18 @@ +module.exports = [ + [ + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0x6B175474E89094C44Da98b954EedeAC495271d0F' + ], + [ + '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4', + '0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46', + '0x773616E4d11A78F511299002da57A0a94577F1f4' + ], + "0x2b13a4AcFF9934f1c60582Dcd5a7db947E74AdEb", + "0x795F84270B085F449A92E943De38062Aa5A2fBa4", + "55000000", + "660000000000000000", + "4140", + "800" +]; \ No newline at end of file diff --git a/scripts/peer-to-peer/dao/mysoMainnetTokenManagerArgs.js b/scripts/peer-to-peer/dao/mysoMainnetTokenManagerArgs.js new file mode 100644 index 00000000..bbdf7bcd --- /dev/null +++ b/scripts/peer-to-peer/dao/mysoMainnetTokenManagerArgs.js @@ -0,0 +1,8 @@ +module.exports = [ + "0xB6307b0f7f85BB5c8DF1569ac1621A78A2Be11A7", + "0x5fde99e121f3ac02e7d6acb081db1f89c1e93c17", + "0x0000000000000000000000000000000000000000", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "0x546881EbC10691072ECC0830Dba7E3E7281D9835", + [], + ]; \ No newline at end of file diff --git a/scripts/peer-to-pool/createFundingPool.ts b/scripts/peer-to-pool/createFundingPool.ts new file mode 100644 index 00000000..6d73674e --- /dev/null +++ b/scripts/peer-to-pool/createFundingPool.ts @@ -0,0 +1,27 @@ +import { ethers } from 'hardhat' + +const hre = require('hardhat') + +async function main() { + const [deployer] = await ethers.getSigners() + + console.log(`Running deploy script for peer-to-peer system with account ${deployer.address}...`) + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + console.log(`deployerBal: ${deployerBal}`) + console.log(`network: ${network}`) + console.log(`hardhatNetworkName: ${hardhatNetworkName}`) + console.log(`hardhatChainId: ${hardhatChainId}`) + + const Factory = await ethers.getContractAt('Factory', '0xcbADd492221BB502e2811f74c1F6D3dE7BFe929e') + await Factory.createFundingPool("0xff970a61a04b1ca14834a43f5de4533ebddb5cc8") +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/peer-to-pool/deploy.ts b/scripts/peer-to-pool/deploy.ts index 9cdfdec0..00e4b064 100644 --- a/scripts/peer-to-pool/deploy.ts +++ b/scripts/peer-to-pool/deploy.ts @@ -1,29 +1,42 @@ import { ethers } from 'hardhat' +const hre = require('hardhat') + async function main() { const [deployer] = await ethers.getSigners() console.log(`Running deploy script for peer-to-peer system with account ${deployer.address}...`) + const deployerBal = await ethers.provider.getBalance(deployer.address) + const network = await ethers.getDefaultProvider().getNetwork() + const hardhatNetworkName = hre.network.name + const hardhatChainId = hre.network.config.chainId + console.log(`deployerBal: ${deployerBal}`) + console.log(`network: ${network}`) + console.log(`hardhatNetworkName: ${hardhatNetworkName}`) + console.log(`hardhatChainId: ${hardhatChainId}`) + /* ************************************ */ /* DEPLOYMENT OF SYSTEM CONTRACTS START */ /* ************************************ */ - // deploy loan proposal implementation + const LoanProposalImpl = await ethers.getContractFactory('LoanProposalImpl') const loanProposalImpl = await LoanProposalImpl.connect(deployer).deploy() await loanProposalImpl.deployed() console.log(`LoanProposalImpl deployed to ${loanProposalImpl.address}`) - - // deploy loan proposal factory - const LoanProposalFactory = await ethers.getContractFactory('LoanProposalFactory') - const loanProposalFactory = await LoanProposalFactory.connect(deployer).deploy(loanProposalImpl.address) - await loanProposalFactory.deployed() - console.log(`LoanProposalFactory deployed to ${loanProposalFactory.address}`) + console.log(`npx hardhat verify ${loanProposalImpl.address} --network ${hardhatNetworkName}`) //example funding pool with usdc as deposit token - const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' - const FundingPool = await ethers.getContractFactory('FundingPool') - const fundingPool = await FundingPool.deploy(loanProposalFactory.address, USDC_ADDRESS) - await fundingPool.deployed() + const FundingPoolImpl = await ethers.getContractFactory('FundingPoolImpl') + const fundingPoolImpl = await FundingPoolImpl.deploy() + await fundingPoolImpl.deployed() + console.log(`npx hardhat verify ${fundingPoolImpl.address} --network ${hardhatNetworkName}`) + + // deploy loan proposal factory + const Factory = await ethers.getContractFactory('Factory') + const factory = await Factory.connect(deployer).deploy("0x1759eb38a2923b62562F8AD1c139509af1B1D52e", fundingPoolImpl.address) + await factory.deployed() + console.log(`Factory deployed to ${factory.address}`) + console.log(`npx hardhat verify ${factory.address} ${"0x1759eb38a2923b62562F8AD1c139509af1B1D52e"} ${fundingPoolImpl.address} --network ${hardhatNetworkName}`) } // We recommend this pattern to be able to use async/await everywhere From a62d1ca43a40a829f053020378f3421348c41453 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Thu, 2 May 2024 23:54:29 -0400 Subject: [PATCH 39/42] add mantle oracle --- .../chainlink/chainlinkBaseWithoutCheck.sol | 114 ++++++++++++ .../oracles/custom/MysoMantleUsdOracle.sol | 164 ++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol create mode 100644 contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol diff --git a/contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol b/contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol new file mode 100644 index 00000000..d5b1f150 --- /dev/null +++ b/contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {AggregatorV3Interface} from "../../interfaces/oracles/chainlink/AggregatorV3Interface.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Constants} from "../../../Constants.sol"; +import {Errors} from "../../../Errors.sol"; +import {IOracle} from "../../interfaces/IOracle.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +abstract contract ChainlinkBaseWithoutCheck is IOracle { + // solhint-disable var-name-mixedcase + uint256 public immutable BASE_CURRENCY_UNIT; + mapping(address => address) public oracleAddrs; + + constructor( + address[] memory _tokenAddrs, + address[] memory _oracleAddrs, + uint256 baseCurrencyUnit + ) { + uint256 tokenAddrsLength = _tokenAddrs.length; + if (tokenAddrsLength != _oracleAddrs.length) { + revert Errors.InvalidArrayLength(); + } + uint8 oracleDecimals; + uint256 version; + for (uint256 i; i < tokenAddrsLength; ) { + if (_tokenAddrs[i] == address(0) || _oracleAddrs[i] == address(0)) { + revert Errors.InvalidAddress(); + } + oracleDecimals = AggregatorV3Interface(_oracleAddrs[i]).decimals(); + if (10 ** oracleDecimals != baseCurrencyUnit) { + revert Errors.InvalidOracleDecimals(); + } + version = AggregatorV3Interface(_oracleAddrs[i]).version(); + if (version != 4) { + revert Errors.InvalidOracleVersion(); + } + oracleAddrs[_tokenAddrs[i]] = _oracleAddrs[i]; + unchecked { + ++i; + } + } + BASE_CURRENCY_UNIT = baseCurrencyUnit; + } + + function getPrice( + address collToken, + address loanToken + ) external view virtual returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = Math.mulDiv( + priceOfCollToken, + 10 ** loanTokenDecimals, + priceOfLoanToken + ); + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + virtual + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + + function _getPriceOfToken( + address token + ) internal view virtual returns (uint256 tokenPriceRaw) { + address oracleAddr = oracleAddrs[token]; + if (oracleAddr == address(0)) { + revert Errors.NoOracle(); + } + tokenPriceRaw = _checkAndReturnLatestRoundData(oracleAddr); + } + + function _checkAndReturnLatestRoundData( + address oracleAddr + ) internal view virtual returns (uint256 tokenPriceRaw) { + ( + uint80 roundId, + int256 answer, + , + uint256 updatedAt, + uint80 answeredInRound + ) = AggregatorV3Interface(oracleAddr).latestRoundData(); + if ( + roundId == 0 || + answeredInRound < roundId || + answer < 1 || + updatedAt > block.timestamp || + updatedAt + Constants.MAX_PRICE_UPDATE_TIMESTAMP_DIVERGENCE < + block.timestamp + ) { + revert Errors.InvalidOracleAnswer(); + } + tokenPriceRaw = uint256(answer); + } +} diff --git a/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol new file mode 100644 index 00000000..7800b171 --- /dev/null +++ b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {ChainlinkBaseWithoutCheck} from "../chainlink/ChainlinkBaseWithoutCheck.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {LogExpMath} from "./utils/LogExpMath.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IGLPManager} from "../../interfaces/oracles/IGLPManager.sol"; +import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; + +/** + * @dev supports oracles which are compatible with v2v3 or v3 interfaces + */ +contract MysoMantleUsdOracle is ChainlinkBaseWithoutCheck, Ownable { + struct PriceParams { + // maxPrice is in 8 decimals for chainlink consistency + uint96 maxPrice; + // k is in 18 decimals + // e.g. 8e17 is 0.8 in decimal + uint96 k; + // a and b are in terms of 1000 + // e.g. 1770 is 1.77 in decimal + uint32 a; + uint32 b; + } + // solhint-disable var-name-mixedcase + address internal constant MYSO = 0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12; + address internal constant USDC = 0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9; + + uint256 internal constant MYSO_PRICE_TIME_LOCK = 5 minutes; + + address public mysoTokenManager; + + PriceParams public mysoPriceParams; + + event MysoTokenManagerUpdated(address newMysoTokenManager); + + error NoMyso(); + + /** + * @dev constructor for MysoOracle + * @param _tokenAddrs array of token addresses + * @param _oracleAddrs array of oracle addresses + * @param _owner owner of the contract + * @param _mysoTokenManager address of myso token manager contract + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + constructor( + address[] memory _tokenAddrs, + address[] memory _oracleAddrs, + address _owner, + address _mysoTokenManager, + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) ChainlinkBaseWithoutCheck(_tokenAddrs, _oracleAddrs, 1e8) Ownable() { + mysoTokenManager = _mysoTokenManager; + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + _transferOwnership(_owner); + } + + /** + * @dev updates myso token manager contract address + * @param _newMysoTokenManager new myso token manager contract address + */ + + function setMysoTokenManager( + address _newMysoTokenManager + ) external onlyOwner { + mysoTokenManager = _newMysoTokenManager; + emit MysoTokenManagerUpdated(_newMysoTokenManager); + } + + /** + * @dev updates myso price params + * @param _maxPrice max price in 8 decimals + * @param _k k in 18 decimals + * @param _a a in terms of 1000 + * @param _b b in terms of 1000 + */ + function setMysoPriceParams( + uint96 _maxPrice, + uint96 _k, + uint32 _a, + uint32 _b + ) external onlyOwner { + mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); + } + + function getPrice( + address collToken, + address loanToken + ) external view override returns (uint256 collTokenPriceInLoanToken) { + (uint256 priceOfCollToken, uint256 priceOfLoanToken) = getRawPrices( + collToken, + loanToken + ); + uint256 loanTokenDecimals = (loanToken == MYSO) + ? 18 + : IERC20Metadata(loanToken).decimals(); + collTokenPriceInLoanToken = + (priceOfCollToken * 10 ** loanTokenDecimals) / + priceOfLoanToken; + } + + function getRawPrices( + address collToken, + address loanToken + ) + public + view + override + returns (uint256 collTokenPriceRaw, uint256 loanTokenPriceRaw) + { + // must have at least one token is MYSO to use this oracle + if (collToken != MYSO && loanToken != MYSO) { + revert NoMyso(); + } + (collTokenPriceRaw, loanTokenPriceRaw) = ( + _getPriceOfToken(collToken), + _getPriceOfToken(loanToken) + ); + } + + function _getPriceOfToken( + address token + ) internal view virtual override returns (uint256 tokenPriceRaw) { + if (token == MYSO) { + tokenPriceRaw = _getMysoPriceInUsd(); + } else if (token == USDC) { + tokenPriceRaw = 1e8; + } else { + tokenPriceRaw = super._getPriceOfToken(token); + } + } + + function _getMysoPriceInUsd() + internal + view + returns (uint256 mysoPriceInUsd) + { + uint256 _totalMysoLoanAmount = IMysoTokenManager(mysoTokenManager) + .totalMysoLoanAmount(); + PriceParams memory params = mysoPriceParams; + uint256 maxPrice = uint256(params.maxPrice); + uint256 k = uint256(params.k); + uint256 a = uint256(params.a); + uint256 b = uint256(params.b); + uint256 numerator = k * b; + uint256 denominator = uint256( + LogExpMath.exp( + int256(Math.mulDiv(_totalMysoLoanAmount, a, 1000000000)) + ) + ) + (2 * b - 1000) * 1e15; + mysoPriceInUsd = maxPrice - Math.mulDiv(numerator, 1e5, denominator); + } +} From 31eaea7bdf14550a59da4a0d4206dea178b1fce2 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Fri, 3 May 2024 01:20:22 -0400 Subject: [PATCH 40/42] added in hardcoded IOO mantle oracle --- ...heck.sol => ChainlinkBaseWithoutCheck.sol} | 0 .../oracles/custom/MysoMantleUsdOracle.sol | 4 +-- .../mainnet-myso-oracle-forked-tests.ts | 27 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) rename contracts/peer-to-peer/oracles/chainlink/{chainlinkBaseWithoutCheck.sol => ChainlinkBaseWithoutCheck.sol} (100%) diff --git a/contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol b/contracts/peer-to-peer/oracles/chainlink/ChainlinkBaseWithoutCheck.sol similarity index 100% rename from contracts/peer-to-peer/oracles/chainlink/chainlinkBaseWithoutCheck.sol rename to contracts/peer-to-peer/oracles/chainlink/ChainlinkBaseWithoutCheck.sol diff --git a/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol index 7800b171..41b1d05b 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol @@ -102,9 +102,7 @@ contract MysoMantleUsdOracle is ChainlinkBaseWithoutCheck, Ownable { collToken, loanToken ); - uint256 loanTokenDecimals = (loanToken == MYSO) - ? 18 - : IERC20Metadata(loanToken).decimals(); + uint256 loanTokenDecimals = (loanToken == MYSO) ? 18 : 6; collTokenPriceInLoanToken = (priceOfCollToken * 10 ** loanTokenDecimals) / priceOfLoanToken; diff --git a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts index a9d42185..b413d500 100644 --- a/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts +++ b/test/peer-to-peer/mainnet-myso-oracle-forked-tests.ts @@ -542,5 +542,32 @@ describe('Peer-to-Peer: Myso Recent Forked Mainnet Tests', function () { ) } }) + it('Should set up myso on mantle IOO price correctly', async function () { + const { lender, team, testnetTokenManager } = await setupTest() + + const myso = '0x25bA1ED5DEEA9d8e8add565dA069Ed1eDA397C12' + const usdc = '0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9' + + // deploy myso oracle + const MysoOracle = await ethers.getContractFactory('MysoMantleUsdOracle') + + const mysoOracle = await MysoOracle.connect(lender).deploy( + [], + [], + team.address, + testnetTokenManager.address, + 55000000, + ethers.BigNumber.from('660000000000000000'), + 1650, + 1000 + ) + await mysoOracle.deployed() + + const usdcCollMysoLoanPrice = await mysoOracle.getPrice(usdc, myso) + const mysoCollUsdcLoanPrice = await mysoOracle.getPrice(myso, usdc) + + console.log('usdcCollMysoLoanPrice', ethers.utils.formatUnits(usdcCollMysoLoanPrice, 18)) + console.log('mysoCollUsdcLoanPrice', ethers.utils.formatUnits(mysoCollUsdcLoanPrice, 6)) + }) }) }) From 1ac9d9ed482c27522eab76933839d8630ad71129 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Fri, 3 May 2024 01:22:55 -0400 Subject: [PATCH 41/42] remove unnecessary imports --- contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol index 41b1d05b..f4738027 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoMantleUsdOracle.sol @@ -7,9 +7,6 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {LogExpMath} from "./utils/LogExpMath.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {IGLPManager} from "../../interfaces/oracles/IGLPManager.sol"; -import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces From 7ffb1dd5aeba648abe408969a5da3b9d0fc1d991 Mon Sep 17 00:00:00 2001 From: Jamie Pickett Date: Fri, 24 May 2024 08:58:14 -0400 Subject: [PATCH 42/42] added edits to bnb oracle --- .../peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol b/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol index a1a5e948..717abd99 100644 --- a/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol +++ b/contracts/peer-to-peer/oracles/custom/MysoBnbUsdOracle.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.19; -//import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; -import {ChainlinkArbitrumSequencerUSD} from "../chainlink/ChainlinkArbitrumSequencerUSD.sol"; +import {ChainlinkBase} from "../chainlink/ChainlinkBase.sol"; +//import {ChainlinkArbitrumSequencerUSD} from "../chainlink/ChainlinkArbitrumSequencerUSD.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IMysoTokenManager} from "../../interfaces/oracles/IMysoTokenManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -15,7 +15,7 @@ import {IGTOKEN} from "../../interfaces/oracles/IGTOKEN.sol"; /** * @dev supports oracles which are compatible with v2v3 or v3 interfaces */ -contract MysoBnbUsdOracle is ChainlinkArbitrumSequencerUSD, Ownable { +contract MysoBnbUsdOracle is ChainlinkBase, Ownable { struct PriceParams { // maxPrice is in 8 decimals for chainlink consistency uint96 maxPrice; @@ -61,7 +61,7 @@ contract MysoBnbUsdOracle is ChainlinkArbitrumSequencerUSD, Ownable { uint96 _k, uint32 _a, uint32 _b - ) ChainlinkArbitrumSequencerUSD(_tokenAddrs, _oracleAddrs) Ownable() { + ) ChainlinkBase(_tokenAddrs, _oracleAddrs, 1e8) Ownable() { mysoTokenManager = _mysoTokenManager; mysoPriceParams = PriceParams(_maxPrice, _k, _a, _b); _transferOwnership(_owner);