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); + } +}