Skip to content

Commit

Permalink
added start of math calculation in test token manager
Browse files Browse the repository at this point in the history
  • Loading branch information
jpick713 committed Mar 29, 2024
1 parent 9e84949 commit f22af37
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 43 deletions.
4 changes: 2 additions & 2 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions contracts/peer-to-peer/interfaces/oracles/IMysoTokenManager.sol
Original file line number Diff line number Diff line change
@@ -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);
}
57 changes: 18 additions & 39 deletions contracts/peer-to-peer/oracles/custom/MysoOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@ 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";

/**
* @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;
Expand All @@ -41,57 +36,42 @@ 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();

/**
* @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(
Expand Down Expand Up @@ -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) {
Expand Down
207 changes: 207 additions & 0 deletions contracts/test/LogExpMath.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
22 changes: 22 additions & 0 deletions contracts/test/TestnetTokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit f22af37

Please sign in to comment.