Skip to content

Commit

Permalink
Merge pull request #18 from Ion-Protocol/carson/decimal-testing
Browse files Browse the repository at this point in the history
Carson/decimal testing
  • Loading branch information
CarsonCase authored Sep 6, 2024
2 parents 4b3daa7 + 0364552 commit d0580a3
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 1 deletion.
57 changes: 57 additions & 0 deletions deployment-config/form-lst-testnet-l1-08-30-24.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"base": "0xee44150250AfF3E6aC25539765F056EDb7F85D7B",
"protocolAdmin": "0x0000000000417626Ef34D62C4DC189b021603f2F",
"boringVaultAndBaseDecimals": "18",
"boringVault": {
"boringVaultSalt": "0x1ddd634c506ad203da17ff000000000000000000000000000000000000000013",
"boringVaultName": "Form LST",
"boringVaultSymbol": "FLST",
"address": "0x0000000000000000000000000000000000000000"
},
"manager": {
"managerSalt": "0x30432d4b4ec00003b4a250000000000000000000000000000000000000000013",
"address": "0x0000000000000000000000000000000000000000"
},
"accountant": {
"accountantSalt": "0x6a184dbea6f3cc0318679f000000000000000000000000000000000000000013",
"payoutAddress": "0x0000000000417626Ef34D62C4DC189b021603f2F",
"allowedExchangeRateChangeUpper": "10003",
"allowedExchangeRateChangeLower": "9998",
"minimumUpdateDelayInSeconds": "3600",
"managementFee": "0",
"address": "0x0000000000000000000000000000000000000000"
},
"teller": {
"tellerSalt": "0x51f8968749a56d01202c91000000000000000000000000000000000000000013",
"maxGasForPeer": 100000,
"minGasForPeer": 0,
"peerEid": 40270,
"tellerContractName": "TellerWithMultiAssetSupport",
"opMessenger": "0x58Cc85b8D04EA49cC6DBd3CbFFd00B4B8D6cb3ef",
"assets": [],
"dvnIfNoDefault": {
"required": [
"0x589dEDbD617e0CBcB916A9223F4d1300c294236b"
],
"optional": [
"0x380275805876Ff19055EA900CDb2B46a94ecF20D",
"0x8FafAE7Dd957044088b3d0F67359C327c6200d18",
"0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5",
"0xe552485d02EDd3067FE7FCbD4dd56BB1D3A998D2"
],
"blockConfirmationsRequiredIfNoDefault": 15,
"optionalThreshold": 1
},
"address": "0x0000000000000000000000000000000000000000"
},
"rolesAuthority": {
"rolesAuthoritySalt": "0x66bbc3b3b3000b01466a3a000000000000000000000000000000000000000013",
"strategist": "0x0000000000417626Ef34D62C4DC189b021603f2F",
"exchangeRateBot": "0x0000000000417626Ef34D62C4DC189b021603f2F",
"address": "0x0000000000000000000000000000000000000000"
},
"decoder": {
"decoderSalt": "0x48b53893da2e0b0248268c000000000000000000000000000000000000000013",
"address": "0x0000000000000000000000000000000000000000"
}
}
2 changes: 1 addition & 1 deletion lzConfigCheck.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async function main() {

const chain2 = findings2.findings[0].chain;
for (const finding of findings2.findings) {
assert(providers1.includes(finding.provider), "Provider: "+finding.provider+" does not havea matching provider in the first config");
assert(providers1.includes(finding.provider), "Provider: "+finding.provider+" does not have a matching provider in the first config");
assert(finding.chain == chain2, "Networks do not match for: "+finding);
}

Expand Down
298 changes: 298 additions & 0 deletions test/RateMath.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { FixedPointMathLib } from "@solmate/utils/FixedPointMathLib.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { Test, stdStorage, StdStorage, stdError, console } from "@forge-std/Test.sol";

contract RateMath is Test {
using FixedPointMathLib for uint256;

uint256 constant ACCEPTED_DELTA_PERCENT_OUT_OF_FAVOR = 0.000015e18;
uint256 constant ACCEPTED_DELTA_PERCENT_IN_FAVOR = 0.01e18;

// keep some state variables that each test can change according to the scenario it's testing
uint256 ONE_SHARE;

// exchange rate as reported in base decimals
uint256 exchangeRateInBase;
// base asset decimals, ALSO the exchange rate decimals
uint256 baseDecimals;
// the quote asset decimals
uint256 quoteDecimals;
// decimals returned by rate provider in base per quote
uint256 quoteRateDecimals;
// quote rate returned by rate provider
uint256 quoteRate;

function boundValues(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate
)
internal
returns (uint256 _depositAmount, uint256 _quoteRate, uint256 _exchangeRate)
{
// Bound Deposit 1 - 100,000,000 QuoteDecimals
_depositAmount = bound(depositAmount, 1 * e(quoteDecimals), 100_000_000 * e(quoteDecimals));
// Bound quote rate to 0.01 - 10 QuoteRateDecimals
_quoteRate = bound(startQuoteRate, 1 * e(quoteRateDecimals - 2), 10 * e(quoteRateDecimals));
// bound exchange rate to 0.8 - 2 baseDecimals
_exchangeRate = bound(startExchangeRate, 8 * e(baseDecimals - 1), 2 * e(baseDecimals));
}

function testAtomicDepositAndWithdraw_18Decimals(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate
)
external
{
// set decimals
baseDecimals = 18;
quoteDecimals = 18;
quoteRateDecimals = 18;
ONE_SHARE = 10 ** baseDecimals;

// bound values with helper function
(depositAmount, quoteRate, exchangeRateInBase) = boundValues(depositAmount, startQuoteRate, startExchangeRate);

// get shares out if deposit done
uint256 shares = depositAssetForShares(depositAmount);

// get assets back if all shares are withdrawn immediately
uint256 assetsBack = withdrawSharesForAssets(shares);
assertTrue(assetsBack <= depositAmount, "Users should never get back more assets than they deposited");
assertApproxEqAbs(
assetsBack,
depositAmount,
depositAmount.mulDivDown(ACCEPTED_DELTA_PERCENT_IN_FAVOR, 1e18),
"assetsBack != depositAmount when atomic | In Favor"
);
}

function testDepositAndWithdrawWithExchangeRateChange_18_6_Decimals(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate,
uint256 rateChange
)
external
{
// set decimals
baseDecimals = 18;
quoteDecimals = 6;
quoteRateDecimals = 6;
ONE_SHARE = 10 ** baseDecimals;

// bound values
(depositAmount, startQuoteRate, startExchangeRate) =
boundValues(depositAmount, startQuoteRate, startExchangeRate);
exchangeRateInBase = startExchangeRate;
quoteRate = startQuoteRate;
rateChange = bound(rateChange, 9980, 10_020);

// get shares out if deposit done
uint256 shares = depositAssetForShares(depositAmount);

// update the rate according to rate change
exchangeRateInBase = exchangeRateInBase.mulDivDown(rateChange, 10_000);

uint256 assetsBack = withdrawSharesForAssets(shares);
// get expected amount out
uint256 expected = (depositAmount * exchangeRateInBase * startQuoteRate) / (quoteRate * startExchangeRate);

if (assetsBack > expected) {
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_OUT_OF_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | Out Of Favor"
);
}
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_IN_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | In Favor"
);
}

function testDepositAndWithdrawWithQuoteRateChange_18_6_Decimals(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate,
uint256 rateChange
)
external
{
// set decimals
baseDecimals = 18;
quoteDecimals = 6;
quoteRateDecimals = 6;
ONE_SHARE = 10 ** baseDecimals;

// bound values
(depositAmount, startQuoteRate, startExchangeRate) =
boundValues(depositAmount, startQuoteRate, startExchangeRate);
exchangeRateInBase = startExchangeRate;
quoteRate = startQuoteRate;
rateChange = bound(rateChange, 9980, 10_020);

// get shares out if deposit done
uint256 shares = depositAssetForShares(depositAmount);

// update the rate according to rate change
quoteRate = quoteRate.mulDivDown(rateChange, 10_000);

uint256 assetsBack = withdrawSharesForAssets(shares);

// get expected amount out
uint256 expected = (depositAmount * exchangeRateInBase * startQuoteRate) / (quoteRate * startExchangeRate);

if (assetsBack > expected) {
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_OUT_OF_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | Out Of Favor"
);
}
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_IN_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | In Favor"
);
}

function testDepositAndWithdrawWithAllFuzzed_18_decimals(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate,
uint256 exchangeRateChange,
uint256 quoteRateChange
)
external
{
// set decimals
baseDecimals = 18;
quoteDecimals = 18;
quoteRateDecimals = quoteDecimals;
ONE_SHARE = 10 ** baseDecimals;

// bound values
(depositAmount, startQuoteRate, startExchangeRate) =
boundValues(depositAmount, startQuoteRate, startExchangeRate);
exchangeRateInBase = startExchangeRate;
quoteRate = startQuoteRate;
exchangeRateChange = bound(exchangeRateChange, 5980, 20_020);
quoteRateChange = bound(quoteRateChange, 5980, 20_020);

// get shares out if deposit done
uint256 shares = depositAssetForShares(depositAmount);

// update the rate according to rate change
exchangeRateInBase = exchangeRateInBase.mulDivDown(exchangeRateChange, 10_000);
quoteRate = quoteRate.mulDivDown(quoteRateChange, 10_000);

uint256 expected = (depositAmount * exchangeRateInBase * startQuoteRate) / (quoteRate * startExchangeRate);

uint256 assetsBack = withdrawSharesForAssets(shares);

if (assetsBack > expected) {
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_OUT_OF_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | Out Of Favor"
);
}
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_IN_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | In Favor"
);
}

function testDepositAndWithdrawWithAllFuzzed_18_6_decimals(
uint256 depositAmount,
uint256 startQuoteRate,
uint256 startExchangeRate,
uint256 exchangeRateChange,
uint256 quoteRateChange
)
external
{
// set decimals
baseDecimals = 18;
quoteDecimals = 6;
quoteRateDecimals = quoteDecimals;
ONE_SHARE = 10 ** baseDecimals;

// bound values
(depositAmount, startQuoteRate, startExchangeRate) =
boundValues(depositAmount, startQuoteRate, startExchangeRate);
exchangeRateInBase = startExchangeRate;
quoteRate = startQuoteRate;
exchangeRateChange = bound(exchangeRateChange, 5980, 20_020);
quoteRateChange = bound(quoteRateChange, 5980, 20_020);

// get shares out if deposit done
uint256 shares = depositAssetForShares(depositAmount);

// update the rate according to rate change
exchangeRateInBase = exchangeRateInBase.mulDivDown(exchangeRateChange, 10_000);
quoteRate = quoteRate.mulDivDown(quoteRateChange, 10_000);

uint256 expected = (depositAmount * exchangeRateInBase * startQuoteRate) / (quoteRate * startExchangeRate);

uint256 assetsBack = withdrawSharesForAssets(shares);

if (assetsBack > expected) {
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_OUT_OF_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | Out Of Favor"
);
}
assertApproxEqAbs(
assetsBack,
expected,
expected.mulDivDown(ACCEPTED_DELTA_PERCENT_IN_FAVOR, 1e18),
"assetsBack != depositAmount with rate change | In Favor"
);
}

function withdrawSharesForAssets(uint256 shareAmount) public view returns (uint256 assetsOut) {
assetsOut = shareAmount.mulDivDown(getRateInQuote(), ONE_SHARE);
}

function depositAssetForShares(uint256 depositAmount) public view returns (uint256 shares) {
if (depositAmount == 0) revert("depositAssetForShares amount = 0");
shares = depositAmount.mulDivDown(ONE_SHARE, getRateInQuote());
}

function getRateInQuote() public view returns (uint256 rateInQuote) {
uint256 exchangeRateInQuoteDecimals = changeDecimals(exchangeRateInBase, baseDecimals, quoteDecimals);
uint256 oneQuote = 10 ** quoteDecimals;
rateInQuote = oneQuote.mulDivDown((exchangeRateInQuoteDecimals), quoteRate);
}

function changeDecimals(uint256 amount, uint256 fromDecimals, uint256 toDecimals) internal pure returns (uint256) {
if (fromDecimals == toDecimals) {
return amount;
} else if (fromDecimals < toDecimals) {
return amount * 10 ** (toDecimals - fromDecimals);
} else {
return amount / 10 ** (fromDecimals - toDecimals);
}
}

/// @dev Helper function to perform 10**x
function e(uint256 decimals) internal pure returns (uint256) {
return (10 ** decimals);
}
}

0 comments on commit d0580a3

Please sign in to comment.