Skip to content

Commit 50bf418

Browse files
CheyenneAtapourDhairyaSethiyan-man
authored
Spoke User RP Tests (#146)
* LH Refactor (#92) * feat: Naive incremental avg calc * wip: Poc risk premium calc * feat: Skeleton code for risk premium calc * feat: Fix lh rp math according to new specs * fix: Name changes for clarity wrt new design * feat: Borrow rate * feat: Split interest accrual and br calcs * feat: Use spoke drawn shares in LH br calc * fix: Simplify names * fix: Remove unused code * test: Initial borrow rate test * test: Supplying w same risk premium doesnt affect borrow rate * feat: LH borrow rate calc * wip: Adjust math on rp calcs * wip: Update interest accrual, debugging tests * fix: No accrual if zero drawn * fix: Add back remainder of test * wip: Supply test failing * wip: Add todos * fix: Update accrue interest function * fix: Pass risk premium and weight separately * wip: 2 spokes drawing fuzz test * test: Fuzz risk premium with 2 spokes * test: Add fuzz test rp 3 spokes * fix: Stack too deep issue * fix: Rename test * wip: Final changes before new algo * Resolve supply token transfer flow (#76) * fix: Change token transfer flow during supply action * test: Resolve approvals in tests * test: Resolve broken tests due to approvals * test: Resolve broken tests * test: Add tests for insufficient allowance * chore: Remove console2 import * fix: Rename user to supplier; resolve tests with updated realistic setup * test: Resolve tests * fix: Resolve supply param * test: Mint set value to user in tests * test: Skip over vm.warp * test: Fix unnecesary casting * Resolve restore token flow (#77) * upd: restore token flow based on latest spec * upd: settler => repayer * test: Resolve unnecessary casting --------- Co-authored-by: dhairya <55102840+DhairyaSethi@users.noreply.github.com> * wip: Refactor liquidity hub * fix: Update borrow index and rename spoke shares * wip: Refactor spoke * wip: Refactoring tests * fix: asset and supply amounts distinction * fix: lib usage, use percentageMath abstractions over constants * chore: clean getTotalAssets * fix: cleanup asset conversion helpers * fix: normalise new var usage * chore: bring soke and interface up to date * wip tests refactor * chore: cleanup accrue interest existing impl * feat: fix weighted avg calc * test: comment new changes, test refactor needed, project compiles * test: add `toMigrate` suffix * chore: use abs imports * fix: use correct available assets for exchange ratio * chore: add decimals * test: configure enviornment * wip: liquidity hub risk premium test * feat(weightedAvgLib): add precision before using lib * feat: fix rp to rad and use in bps, room for more precision * feat: LiquidityHub.Spoke => SpokeData * feat: asset logic lib * feat: spoke data logic lib * todo: bug in interest rate strat * test: liquidity hub risk premium base two scenario tests * fix: use correct virtualUnderlyingBalance * feat: make asset logic lib external --------- Co-authored-by: Cheyenne Atapour <cheyenneatapour@gmail.com> Co-authored-by: YBM <yan@avara.xyz> * Short term bugfix for DefaultReserveInterestRateStrategy (#99) * fix: Return values in ray * chore: Add todo comment * Fix _addSpoke struct order (#101) * fix: Resolve _addSpoke struct order * refactor: Params -> config consistent naming * feat: radMul optimise interest calc (#106) * Refactor Borrow Rate Tests (#109) * fix: Wad math for borrow rate * fix: Borrow rate test * fix: Proper math on basic borrow rate tests * fix: 2 spoke test * feat: radMul optimise interest calc * feat: Apply rad math for interest rate * fix: Change test to exactly equal * wip: Test 2 spokes diff weights * wip: Two spokes diff weights test simplify math * test: Fix everything except 3 spoke fuzz * chore: Cleanup comments * fix: Use rp from lh for 3 spoke fuzz test * fix: Pr nits * fix: Test naming conventions * fix: Remove lh prefix in test names --------- Co-authored-by: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> * chore: skip pending tests (#113) * LH `supply` implementation and resolve LH tests (#108) * test: Assertions on spokeData * test: Show sharesAmount accounting bug * test: fix LH supply accounting, post shares conversion * test: resolve supply tests * test: test_supply_revertsWith_invalid_amount, update shares error * test: test_supply_revertsWith_invalid_shares_amount * test: clarify storage slots * test: remove loading lib in test * fix: no need to recast * feat: update supply inputs to RP rad; fix draw test * test: resolve remaining tests in LH * chore: remove unneeded casting, clean up vm utils call * chore: remove console2 * chore: remove console2 * test: add assertion * chore: remove skip * chore: rename LH test * LH `withdraw` refactor and implementation (#115) * test: Assertions on spokeData * test: Show sharesAmount accounting bug * test: fix LH supply accounting, post shares conversion * test: resolve supply tests * test: test_supply_revertsWith_invalid_amount, update shares error * test: test_supply_revertsWith_invalid_shares_amount * test: clarify storage slots * test: remove loading lib in test * fix: no need to recast * feat: update supply inputs to RP rad; fix draw test * test: resolve remaining tests in LH * chore: remove unneeded casting, clean up vm utils call * chore: remove console2 * chore: remove console2 * test: add assertion * fix: resolve spoke shares after withdraw, resolve test * test: test_withdraw_fuzz_events * test: clarify assertions, resolve test_fuzz_first_supply * test: test_supply_fuzz_multi * test: test_withdraw_revertsWith_supplied_amount_exceeded * chore: optimize unnecessary recasting, add method to interface * chore: optimize utils * chore: remove console2 * chore: todo next * chore: remove short circuit from test * chore: refactor according to code conventions * test: fix bug * test: revert casting to MockPriceOracle * chore: bps format convention * chore: default to vm.skip * chore: avoid uint cast * test: cleanup, use vm block timestamp * chore: use vm timestamp * test: add randomizer in base test * chore: remove the internal method convention for tests * chore: cast oracle in base test * chore: fix remaining oracles casting in tests * chore: remove todo comments * refactor: resolve test_supply, use init env * refactor: resolve supply tests * test: resolve withdraw tests * test: resolve draw tests * test: resolve test_restore * test: resolve restore tests * refactor: finish migration to initEnv * test: resolve draw tests with base test vars * test: resolve tests, migration * test: test_first_draw_same_block * chore: clarify test names * chore: clean up tests covered in separate file * chore: rename tests according to conventions; draw fuzz test * test: optimize, remove var * test: test_restore_same_block * chore: named struct syntax * chore: named input arg syntax * feat: return baseBorrowIndex from LH methods, update natspec * refactor: update test utils to allow RP input; update spoke according to LH outputs * test: test_supply_with_increased_index * test: remove console.log; test_supply_with_increased_index with RP * test: test_supply_with_increased_index_with_premium * test: assertions on test_supply_with_increased_index * chore: optimization on calc * test: test_withdraw_all_with_interest * test: clean up assertions for test_withdraw_all_with_interest * test: test_restore_revertsWith_invalid_restore_amount_with_interest * test: test_restore_fuzz_revertsWith_invalid_restore_amount_with_interest * test: test_restore_revertsWith_invalid_restore_amount clean up minimal path * test: test_restore_fuzz_revertsWith_invalid_restore_amount_with_interest_and_premium * refactor: _setUpIncreasedIndex with rate input * refactor: named arg syntax for _setUpIncreasedIndex * test: test_restore_partial_premium; add some todo * test: test_restore_fuzz_partial_premium * fix: revert supply return vars * test: test_restore_partial_premium_and_base; move internal method down * test: expand bounds; test_restore_fuzz_partial_premium_and_base * chore: add some comments * test: consolidate tests into test_supply_fuzz * test: test_supply_fuzz_multi_asset_multi_spoke * chore: remove comments * test: resolve fuzz test condition * test: clean up and remove some redundancy * chore: revert withdraw return params; clean up natspec, console log * chore: remove unneeded vars * test: optimize, remove unneeded * refactor: add approvals to basetest setup * refactor: remove unneeded fuzz input * test: test_withdraw_fuzz_all_with_interest * chore: remove some comments * chore: revert changes to supply return args * chore: remove unneeded var * chore: migrate mock users, resolve tests * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve tests * chore: resolve tests * chore: resolve tests * chore: resolve tests * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve tests, remove deal * chore: resolve bound granularity * chore: natspec, move to interface, update utils * chore: typo * chore: onBehalfOf -> to * chore: revert some return sigs * chore: update natspec * chore: resolve compilation * chore: inheritdoc * chore: revert return sig * chore: rename internal method * chore: update error msg * chore: restoreAmount > 0 * chore: clean up pranks; short circuit on zero accrued * chore: resolve tests * chore: resolve tests, cleanup * chore: set a var to reuse; clean up prank * refactor: re-arrange method args * chore: method args * refactor: naming * chore: clean up prank * refactor: load hub data more clearly through each action within test * Accrue Interest Tests (#129) * test: Simple base interest accrual * test: Fuzz borrow amount and time elapsed * wip: Outstanding rp test * test: Accrue interest 10 percent rp * test: Fuzz rp borrow amount and elapsed * fix: Pr nits * wip: Debugging accrual test changing rate * wip: Testing borrow rate accrual when changed * fix: Changing borrow rate accrual test * feat: Move deal and approve to base test * chore: Cleanup unneeded variables * style: Use utils where appropriate * test: Import utils relatively * fix: Remove extraneous arg from utils calls * style: Revert utils import style * fix: resolve merged dev changes * refactor: break out tests into separate file * refactor: clean up file locations * refactor: LH config tests separated * chore: remove unneeded var * chore: whitespace * feat: Add new vars, skeleton supply code * fix: Reorder code skeleton * feat: Preview next borrow rate for accruing interest * Resolve LH `draw`/`restore` (#128) * feat: Implement accrue interest * feat: Add liquidity premium to assets on hub * feat: Implement updating single user risk premium * feat: User specific accrue interest * feat: Implement weighted avg rp on spoke * style: Update variable names in update rp * fix: Calculation of cumulated base debt * fix: Single user rp calc * fix: Remove legacy variables * test: Unskip spoke tests * test: Migrate existing supply tests * test: Supply fuzz amounts * fix: Requested PR changes * test: Spoke supply insufficient allowance * test: Move wbtc id to base test * style: Replace simple helper functions * feat: Withdraw implementation * feat: Implement borrow * feat: Mapping reserveId to assetId * style: Move assetId to reserve struct * fix: Remove sorting from user risk premium calc * style: Change storage layout of users * fix: Revert removal of helper functions * feat: Implement repay * fix: Remove self-referential reserve id * fix: Usage of storage * todo: Note where we can use shared logic library * test: Check reserve supply shares * chore: Remove todo * test: No debt accrue interest tests * fix: Only repay max debt * fix: Only allow withdraw up to supplied amount * fix: Allow max uint256 withdrawal * fix: Only allow restoring up to max debt * fix: Use assetId, revert hub changes * fix: Cache supplied amount * fix: Add back todos * fix: Revert max withdraw and repay changes * test: Borrow on spoke * fix: Remove unused variable * fix: Remove duplicated base debt adjustment logic * test: Borrow fuzz amounts * fix: Check 0 on LH functions * test: 0 borrow amount * test: Organize borrow and supply tests * fix: Remove todos for max uint on hub * fix: Misc pr fixes * test: Use test errors library * fix: Use assetId when converting to shares * test: Use reserve id on spoke * test: Invalid amount tests * test: Different reserve ids vs asset ids * test: Add extra dai asset in base * test: Nonzero liquidity premiums * fix: Inline return reserveId * test: Create spoke info struct mapping * test: Add dai2 ir. poc can't add same assetId twice to spoke * fix: Misc pr changes requested * fix: Do not add spoke again to lh for same asset * fix: Minor requested changes on pr * fix: Reorder params borrow and withdraw * Add Spoke Logic (#140) * refactor: resolve test_supply, use init env * refactor: resolve supply tests * test: resolve withdraw tests * test: resolve draw tests * test: resolve test_restore * test: resolve restore tests * refactor: finish migration to initEnv * test: resolve draw tests with base test vars * test: resolve tests, migration * test: test_first_draw_same_block * chore: clarify test names * chore: clean up tests covered in separate file * chore: rename tests according to conventions; draw fuzz test * test: optimize, remove var * test: test_restore_same_block * chore: named struct syntax * chore: named input arg syntax * feat: return baseBorrowIndex from LH methods, update natspec * refactor: update test utils to allow RP input; update spoke according to LH outputs * test: test_supply_with_increased_index * test: remove console.log; test_supply_with_increased_index with RP * test: test_supply_with_increased_index_with_premium * test: assertions on test_supply_with_increased_index * chore: optimization on calc * test: test_withdraw_all_with_interest * test: clean up assertions for test_withdraw_all_with_interest * test: test_restore_revertsWith_invalid_restore_amount_with_interest * test: test_restore_fuzz_revertsWith_invalid_restore_amount_with_interest * test: test_restore_revertsWith_invalid_restore_amount clean up minimal path * test: test_restore_fuzz_revertsWith_invalid_restore_amount_with_interest_and_premium * refactor: _setUpIncreasedIndex with rate input * refactor: named arg syntax for _setUpIncreasedIndex * test: test_restore_partial_premium; add some todo * test: test_restore_fuzz_partial_premium * fix: revert supply return vars * test: test_restore_partial_premium_and_base; move internal method down * test: expand bounds; test_restore_fuzz_partial_premium_and_base * chore: add some comments * test: consolidate tests into test_supply_fuzz * test: test_supply_fuzz_multi_asset_multi_spoke * chore: remove comments * test: resolve fuzz test condition * test: clean up and remove some redundancy * chore: revert withdraw return params; clean up natspec, console log * chore: remove unneeded vars * test: optimize, remove unneeded * refactor: add approvals to basetest setup * refactor: remove unneeded fuzz input * test: test_withdraw_fuzz_all_with_interest * chore: remove some comments * chore: revert changes to supply return args * chore: remove unneeded var * chore: migrate mock users, resolve tests * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve tests * chore: resolve tests * chore: resolve tests * chore: resolve tests * chore: resolve test * chore: resolve test * chore: resolve test * chore: resolve tests, remove deal * chore: resolve bound granularity * chore: natspec, move to interface, update utils * chore: typo * chore: onBehalfOf -> to * chore: revert some return sigs * chore: update natspec * chore: resolve compilation * chore: inheritdoc * chore: revert return sig * chore: rename internal method * chore: update error msg * chore: restoreAmount > 0 * chore: clean up pranks; short circuit on zero accrued * chore: resolve tests * chore: resolve tests, cleanup * chore: set a var to reuse; clean up prank * refactor: re-arrange method args * chore: method args * refactor: naming * chore: clean up prank * refactor: load hub data more clearly through each action within test * fix: resolve merged dev changes * refactor: break out tests into separate file * refactor: clean up file locations * refactor: LH config tests separated * chore: remove unneeded var * chore: whitespace * feat: Add new vars, skeleton supply code * fix: Reorder code skeleton * feat: Preview next borrow rate for accruing interest * feat: Implement accrue interest * feat: Add liquidity premium to assets on hub * feat: Implement updating single user risk premium * feat: User specific accrue interest * feat: Implement weighted avg rp on spoke * style: Update variable names in update rp * fix: Calculation of cumulated base debt * fix: Single user rp calc * fix: Remove legacy variables * test: Unskip spoke tests * test: Migrate existing supply tests * test: Supply fuzz amounts * fix: Requested PR changes * Rebuild Liquidity Hub (#103) * test: Spoke supply insufficient allowance * test: Move wbtc id to base test * style: Replace simple helper functions * feat: Withdraw implementation * feat: Implement borrow * feat: Mapping reserveId to assetId * style: Move assetId to reserve struct * fix: Remove sorting from user risk premium calc * style: Change storage layout of users * fix: Revert removal of helper functions * feat: Implement repay * fix: Remove self-referential reserve id * fix: Usage of storage * todo: Note where we can use shared logic library * test: Check reserve supply shares * chore: Remove todo * test: No debt accrue interest tests * fix: Only repay max debt * fix: Only allow withdraw up to supplied amount * fix: Allow max uint256 withdrawal * fix: Only allow restoring up to max debt * fix: Use assetId, revert hub changes * fix: Cache supplied amount * fix: Add back todos * fix: Revert max withdraw and repay changes * test: Borrow on spoke * fix: Remove unused variable * fix: Remove duplicated base debt adjustment logic * test: Borrow fuzz amounts * fix: Check 0 on LH functions * test: 0 borrow amount * fix: Remove todos for max uint on hub * fix: Misc pr fixes * test: Use test errors library * fix: Use assetId when converting to shares * test: Use reserve id on spoke * test: Invalid amount tests * test: Different reserve ids vs asset ids * test: Add extra dai asset in base * test: Nonzero liquidity premiums * fix: Inline return reserveId * test: Create spoke info struct mapping * test: Add dai2 ir. poc can't add same assetId twice to spoke * fix: Misc pr changes requested * fix: Do not add spoke again to lh for same asset * fix: Minor requested changes on pr * fix: Reorder params borrow and withdraw * todo: Supply tests * fix: Asset id for lh functions * fix: Remove case of same assetId listed twice on spoke * fix: Remove unneeded struct * fix: Reorder event params * fix: Clarifying comment for dai2 case --------- Co-authored-by: YBM <yan@avara.xyz> Co-authored-by: dhairya <55102840+DhairyaSethi@users.noreply.github.com> * Fix: BaseBorrowIndex (#166) * wip: works so far * chore: cleanup * misc * poc: suppliers can withdraw 2x and change right after supplying * poc: invalid rounding leads infinite to 1 wei withdraw * chore: cleanup fullMath & unnecessary interface * feat: borrow index fix * feat: withdraw shares infinite dust fix * test: scenario tests, LiquidityHubAccrueAssetInterestDynamicTimeTest fixed from #163 * chore: rm hotfix * feat: fix index issue on spoke * chore: minor opt * chore: rm test remenant * fix: resolve test_addSpoke * fix: resolve test_supply_multi_supply_minimal_shares * fix: resolve supply tests * fix: resolve test_withdraw_all_with_interest * fix: resolve LiquidityHubWithdrawTest * fix: resolve test_accrueInterest_OnlySupply * test: add skip to have passing CI * fix: syntax with braces * fix: refactor tests; optimize structure * Resolve prior tests broken by LH index/timestamp fix (#170) * fix: passing test * fix: resolve test_accrueInterest_fuzz_ChangingBorrowRate * chore: braces * chore: remove console log; variable bound * chore: clean up, remove comments * fix: set assertion to 1 wei difference * chore: error msg * fix: forge config for internal calls expect reverts since lib is tested inlined * ref: split borrow index tests * test: rm skip * fix: spoke accrue interest * chore: filename typo --------- Co-authored-by: YBM <yan@avara.xyz> * test: Accrue interest via borrow and wait * test: Single collateral user rp * test: User rp fuzz single collateral * test: User rp 2 assets, weth covering both * test: 2 assets covering user rp * test: User risk premium 2 assets equal parts * test: Two assets fuzzed * test: Supply more risky asset doesnt impact user rp * test: Fuzz two assets, fixing borrow amount * test: Three assets fuzzed diff lps * test: User risk premium fuzzing 4 assets * test: Fuzz four assets, then change price of one * cleanup: Remove old comments * wip: Add back dynamic sorting * fix: Use only array size of collateral assets * fix: One line comparator function * fix: Do not assume reserves in order of lp on spoke * test: Fuzz change lp of asset * chore: make hub, oracle immutable * wip: Use struct to pack test variables * fix: Two assets equal part test using struct * fix: Four assets fuzz supply and borrow using struct * fix: Tests to use struct to pack variables * fix: Misc pr fixes * fix: Use assetId in getInterestRate * fix: Spoke accrue iterest tests to use getters * feat: initial spoke fixes * feat: in memory key, value list * fix: Proper asset check for addSpoke * feat: user account data * chore: cleanup unused method * wip: Amount normalization in tests * wip: Fixing user rp tests with normalized amounts * fix: Rule out dust case * test: Normalize by amounts * fix: Remove unnecessary code, fix comment * fix: Use getters, no trivial supply action * feat: Helper function to calc user RP * test: User rp fuzz everything * test: Apply user risk premium on one borrowed reserve * wip: Failing user rp test after accrual * wip: Test 2 reserves 2 users borrowing * test: Accrue interest two assets, cleanup * add snapshots * feat: change max limits in keyValueList * feat: wadify value in base currency * test: add wadify to normalise helper * fix: snapshot * chore: nits, wadify tests * wip: Clean up tests * wip: Failing case of reserve outstanding premium not sum of users * debug: Show console logs * chore: Cleanup tests * fix: Pr comments * test: Non fuzzed 2 users 2 reserves borrowed * test: Show rp percolates correctly * fix: Misc pr comments * fix: Address pr comments * refactor: Test structs * fix: Pr comments, add test descriptions * fix: Simplify test structs * fix: Remove unnecessary setups --------- Co-authored-by: dhairya <55102840+DhairyaSethi@users.noreply.github.com> Co-authored-by: YBM <yan@avara.xyz>
1 parent d5e5380 commit 50bf418

8 files changed

Lines changed: 1822 additions & 163 deletions

File tree

src/contracts/LiquidityHub.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ contract LiquidityHub is ILiquidityHub {
501501
lastUpdateTimestamp: 0,
502502
config: config
503503
});
504+
504505
emit SpokeAdded(assetId, spoke);
505506
}
506507

tests/Base.t.sol

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ abstract contract Base is Test {
266266
paused: false,
267267
collateralFactor: 75_00,
268268
liquidationBonus: 0,
269-
liquidityPremium: 50_00,
269+
liquidityPremium: 5_00,
270270
borrowable: true,
271271
collateral: true
272272
});
@@ -621,6 +621,11 @@ abstract contract Base is Test {
621621
return spokeInfo[spoke].wbtc.reserveId;
622622
}
623623

624+
// assumes spoke has dai2 supported
625+
function dai2ReserveId(ISpoke spoke) internal view returns (uint256) {
626+
return spokeInfo[spoke].dai2.reserveId;
627+
}
628+
624629
function updateDrawCap(
625630
ILiquidityHub hub,
626631
uint256 assetId,
@@ -651,16 +656,38 @@ abstract contract Base is Test {
651656
ISpoke spoke,
652657
uint256 reserveId
653658
) internal view returns (DataTypes.Reserve memory) {
654-
DataTypes.Reserve memory reserveData = spoke.getReserve(reserveId);
659+
DataTypes.Reserve memory reserveData;
660+
reserveData.reserveId = reserveId;
661+
IERC20 asset;
662+
(reserveData.assetId, asset) = getAssetByReserveId(spoke, reserveId);
663+
reserveData.asset = address(asset);
655664
(reserveData.baseDebt, reserveData.outstandingPremium) = spoke.getReserveDebt(reserveId);
656665
reserveData.suppliedShares = spoke.getReserveSuppliedShares(reserveId);
657666
reserveData.riskPremium = spoke.getReserveRiskPremium(reserveId);
658-
reserveData.lastUpdateTimestamp = reserveData.lastUpdateTimestamp;
659-
reserveData.baseBorrowIndex = reserveData.baseBorrowIndex;
667+
reserveData.lastUpdateTimestamp = spoke.getReserve(reserveId).lastUpdateTimestamp;
668+
reserveData.baseBorrowIndex = spoke.getReserve(reserveId).baseBorrowIndex;
669+
reserveData.config = spoke.getReserve(reserveId).config;
660670
return reserveData;
661671
}
662672

663-
function getAssetInfo(ISpoke spoke, uint256 reserveId) internal view returns (uint256, IERC20) {
673+
function getAssetInfo(uint256 assetId) internal view returns (DataTypes.Asset memory) {
674+
DataTypes.Asset memory asset;
675+
asset.id = assetId;
676+
asset.suppliedShares = hub.getAssetSuppliedShares(assetId);
677+
asset.availableLiquidity = hub.getAvailableLiquidity(assetId);
678+
(asset.baseDebt, asset.outstandingPremium) = hub.getAssetDebt(assetId);
679+
asset.baseBorrowIndex = hub.getAsset(assetId).baseBorrowIndex;
680+
asset.baseBorrowRate = hub.getBaseInterestRate(assetId);
681+
asset.riskPremium = hub.getAssetRiskPremium(assetId);
682+
asset.lastUpdateTimestamp = hub.getAsset(assetId).lastUpdateTimestamp;
683+
asset.config = hub.getAssetConfig(assetId);
684+
return asset;
685+
}
686+
687+
function getAssetByReserveId(
688+
ISpoke spoke,
689+
uint256 reserveId
690+
) internal view returns (uint256, IERC20) {
664691
DataTypes.Reserve memory reserve = spoke.getReserve(reserveId);
665692
return (reserve.assetId, IERC20(reserve.asset));
666693
}

tests/unit/Spoke/Spoke.Supply.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ contract SpokeSupplyTest is SpokeBase {
339339

340340
SupplyFuzzLocal memory state;
341341

342-
(state.assetId, state.asset) = getAssetInfo(spoke1, reserveId);
342+
(state.assetId, state.asset) = getAssetByReserveId(spoke1, reserveId);
343343

344344
state.expectedShares = hub.convertToShares(state.assetId, amount);
345345
vm.assume(state.expectedShares > 0);
@@ -494,7 +494,7 @@ contract SpokeSupplyTest is SpokeBase {
494494
reserveId = bound(reserveId, 0, spokeInfo[spoke1].MAX_RESERVE_ID);
495495
skipTime = bound(skipTime, 1, MAX_SKIP_TIME);
496496

497-
(uint256 assetId, IERC20 asset) = getAssetInfo(spoke1, reserveId);
497+
(uint256 assetId, IERC20 asset) = getAssetByReserveId(spoke1, reserveId);
498498

499499
// alice supplies usdx as collateral, borrows dai
500500
_executeSpokeSupplyAndBorrow({

tests/unit/Spoke/Spoke.Withdraw.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ contract SpokeWithdrawTest is SpokeBase {
315315
abi.encode(params.rate)
316316
);
317317

318-
(state.assetId, state.asset) = getAssetInfo(spoke1, params.reserveId);
318+
(state.assetId, state.asset) = getAssetByReserveId(spoke1, params.reserveId);
319319

320320
// alice supplies reserve
321321
Utils.spokeSupply({
@@ -628,7 +628,7 @@ contract SpokeWithdrawTest is SpokeBase {
628628
// don't borrow the collateral asset
629629
vm.assume(params.reserveId != wbtcReserveId(spoke1));
630630

631-
(uint256 assetId, IERC20 asset) = getAssetInfo(spoke1, params.reserveId);
631+
(uint256 assetId, IERC20 asset) = getAssetByReserveId(spoke1, params.reserveId);
632632

633633
// set weth LP to 0 for no premium contribution
634634
updateLiquidityPremium({
@@ -893,7 +893,7 @@ contract SpokeWithdrawTest is SpokeBase {
893893

894894
vm.assume(params.reserveId != wbtcReserveId(spoke1)); // wbtc used as collateral
895895

896-
(uint256 assetId, IERC20 asset) = getAssetInfo(spoke1, params.reserveId);
896+
(uint256 assetId, IERC20 asset) = getAssetByReserveId(spoke1, params.reserveId);
897897

898898
TestState memory state;
899899
state.reserveId = params.reserveId;
Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,120 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.0;
33

4-
import 'tests/Base.t.sol';
4+
import 'tests/unit/Spoke/SpokeBase.t.sol';
55
import {Spoke} from 'src/contracts/Spoke.sol';
6+
import {LiquidityHub} from 'src/contracts/LiquidityHub.sol';
67

7-
contract SpokeAccrueInterestTest is Base {
8+
contract SpokeAccrueInterestTest is SpokeBase {
89
using SharesMath for uint256;
910
using WadRayMath for uint256;
1011
using PercentageMath for uint256;
1112

12-
uint256 public constant MAX_BPS = 999_99;
13-
14-
function setUp() public override {
15-
super.setUp();
16-
initEnvironment();
17-
}
18-
13+
/// No interest should accrue when no action is taken.
1914
function test_accrueInterest_NoActionTaken() public {
20-
DataTypes.Reserve memory daiInfo = spoke1.getReserve(spokeInfo[spoke1].dai.reserveId);
21-
assertEq(daiInfo.lastUpdateTimestamp, 0);
22-
assertEq(daiInfo.baseDebt, 0);
23-
assertEq(daiInfo.outstandingPremium, 0);
24-
assertEq(daiInfo.riskPremium, 0);
15+
DataTypes.Reserve memory wethInfo = getReserveInfo(spoke1, wethReserveId(spoke1));
16+
assertEq(wethInfo.lastUpdateTimestamp, 0);
17+
assertEq(wethInfo.baseDebt, 0);
18+
assertEq(wethInfo.outstandingPremium, 0);
19+
assertEq(wethInfo.riskPremium, 0);
2520
}
2621

22+
/// Supply an asset only, and check no interest accrued.
2723
function test_accrueInterest_OnlySupply(uint40 elapsed) public {
2824
uint256 amount = 1000e18;
25+
uint256 wethReserveId = wethReserveId(spoke1);
2926

3027
// Bob supplies through spoke 1
31-
Utils.spokeSupply(spoke1, spokeInfo[spoke1].dai.reserveId, bob, amount, bob);
28+
Utils.spokeSupply(spoke1, wethReserveId, bob, amount, bob);
29+
30+
uint256 lastUpdate = vm.getBlockTimestamp();
3231

3332
// Time passes
3433
skip(elapsed);
3534

36-
// Alice does a supply through same spoke to accrue interest
37-
Utils.spokeSupply(spoke1, spokeInfo[spoke1].dai.reserveId, alice, amount, alice);
38-
39-
DataTypes.Reserve memory daiInfo = spoke1.getReserve(spokeInfo[spoke1].dai.reserveId);
35+
DataTypes.Reserve memory wethInfo = getReserveInfo(spoke1, wethReserveId);
4036

4137
// Timestamp doesn't update when no interest accrued
42-
assertEq(daiInfo.lastUpdateTimestamp, vm.getBlockTimestamp(), 'lastUpdateTimestamp');
43-
assertEq(daiInfo.baseDebt, 0, 'baseDebt');
44-
assertEq(daiInfo.riskPremium, 0, 'riskPremium');
45-
assertEq(daiInfo.outstandingPremium, 0, 'outstandingPremium');
38+
assertEq(wethInfo.lastUpdateTimestamp, lastUpdate, 'lastUpdateTimestamp');
39+
assertEq(wethInfo.baseDebt, 0, 'baseDebt');
40+
assertEq(wethInfo.outstandingPremium, 0, 'outstandingPremium');
4641
}
42+
43+
/// Supply and draw a reserve, wait a year, and check interest accrued.
44+
function test_accrueInterest_BorrowAndWait() public {
45+
uint256 amount = 1000e18;
46+
uint256 wethReserveId = wethReserveId(spoke1);
47+
uint256 startTime = vm.getBlockTimestamp();
48+
49+
// Bob supplies and borrows through spoke 1
50+
Utils.spokeSupply(spoke1, wethReserveId, bob, amount * 2, bob);
51+
Utils.spokeBorrow(spoke1, wethReserveId, bob, amount, bob);
52+
53+
uint256 baseBorrowRate = hub.getBaseInterestRate(wethAssetId);
54+
uint256 lastUpdate = vm.getBlockTimestamp();
55+
56+
// 1 year passes
57+
skip(365 days);
58+
59+
DataTypes.Reserve memory wethReserveInfo = getReserveInfo(spoke1, wethReserveId);
60+
DataTypes.Asset memory wethAssetInfo = getAssetInfo(wethAssetId);
61+
62+
uint256 totalBase = MathUtils.calculateLinearInterest(baseBorrowRate, uint40(startTime)).rayMul(
63+
amount
64+
);
65+
66+
// Spoke checks
67+
assertEq(wethReserveInfo.lastUpdateTimestamp, lastUpdate, 'lastUpdateTimestamp');
68+
assertEq(wethReserveInfo.baseDebt, totalBase, 'baseDebt');
69+
assertEq(wethReserveInfo.outstandingPremium, 0, 'outstandingPremium');
70+
71+
// LH checks
72+
assertEq(wethAssetInfo.baseDebt, totalBase, 'asset base debt');
73+
assertEq(wethAssetInfo.riskPremium, 0);
74+
assertEq(wethAssetInfo.outstandingPremium, 0);
75+
assertEq(wethAssetInfo.lastUpdateTimestamp, lastUpdate);
76+
}
77+
78+
/// Supply and draw arbitrary amounts of a reserve, wait arbitrary time, and check interest accrued correctly.
79+
function test_accrueInterest_fuzz_BorrowAmountAndElapsed(
80+
uint256 borrowAmount,
81+
uint40 elapsed
82+
) public {
83+
borrowAmount = bound(borrowAmount, 1, MAX_SUPPLY_AMOUNT / 2);
84+
uint256 supplyAmount = borrowAmount * 2;
85+
uint256 startTime = vm.getBlockTimestamp();
86+
uint256 wethReserveId = wethReserveId(spoke1);
87+
88+
// Bob supplies and borrows through spoke 1
89+
Utils.spokeSupply(spoke1, wethReserveId, bob, supplyAmount, bob);
90+
Utils.spokeBorrow(spoke1, wethReserveId, bob, borrowAmount, bob);
91+
92+
uint256 baseBorrowRate = hub.getBaseInterestRate(wethAssetId);
93+
uint256 lastUpdate = vm.getBlockTimestamp();
94+
95+
// Time passes
96+
skip(elapsed);
97+
98+
DataTypes.Reserve memory wethReserveInfo = getReserveInfo(spoke1, wethReserveId);
99+
DataTypes.Asset memory wethAssetInfo = getAssetInfo(wethAssetId);
100+
101+
uint256 totalBase = MathUtils.calculateLinearInterest(baseBorrowRate, uint40(startTime)).rayMul(
102+
borrowAmount
103+
);
104+
105+
// Spoke checks
106+
assertEq(wethReserveInfo.lastUpdateTimestamp, lastUpdate, 'lastUpdateTimestamp');
107+
assertEq(wethReserveInfo.baseDebt, totalBase, 'baseDebt');
108+
assertEq(wethReserveInfo.outstandingPremium, 0, 'outstandingPremium');
109+
110+
// LH checks
111+
assertEq(wethAssetInfo.baseDebt, totalBase);
112+
assertEq(wethAssetInfo.riskPremium, 0);
113+
assertEq(wethAssetInfo.outstandingPremium, 0);
114+
assertEq(wethAssetInfo.lastUpdateTimestamp, lastUpdate);
115+
}
116+
117+
// TODO: test_accrueInterest_TenPercentRP
118+
// TODO: test_accrueInterest_fuzz_RPBorrowAndElapsed
119+
// TODO: test_accrueInterest_fuzz_ChangingBorrowRate
47120
}

tests/unit/Spoke/SpokeBase.t.sol

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
pragma solidity ^0.8.0;
33

44
import 'tests/Base.t.sol';
5+
import {KeyValueListInMemory} from 'src/contracts/KeyValueListInMemory.sol';
56

67
contract SpokeBase is Base {
78
using PercentageMath for uint256;
9+
using KeyValueListInMemory for KeyValueListInMemory.List;
810

911
struct TestData {
1012
DataTypes.Reserve data;
@@ -73,7 +75,7 @@ contract SpokeBase is Base {
7375
borrow.supplyAmount = 100e18;
7476
borrow.borrowAmount = borrow.supplyAmount / 2;
7577

76-
(state.borrowReserveAssetId, ) = getAssetInfo(spoke, borrow.reserveId);
78+
(state.borrowReserveAssetId, ) = getAssetByReserveId(spoke, borrow.reserveId);
7779
(state.collateralSupplyShares, state.borrowSupplyShares) = _executeSpokeSupplyAndBorrow({
7880
spoke: spoke,
7981
collateral: collateral,
@@ -119,8 +121,8 @@ contract SpokeBase is Base {
119121
);
120122
}
121123

122-
(state.collateralReserveAssetId, ) = getAssetInfo(spoke, collateral.reserveId);
123-
(state.borrowReserveAssetId, ) = getAssetInfo(spoke, borrow.reserveId);
124+
(state.collateralReserveAssetId, ) = getAssetByReserveId(spoke, collateral.reserveId);
125+
(state.borrowReserveAssetId, ) = getAssetByReserveId(spoke, borrow.reserveId);
124126
state.collateralSupplyShares = hub.convertToShares(
125127
state.collateralReserveAssetId,
126128
collateral.supplyAmount
@@ -246,4 +248,76 @@ contract SpokeBase is Base {
246248
collData.config.collateralFactor
247249
) + 1;
248250
}
251+
252+
/// @dev Returns the USD value of the reserve normalized by it's decimals, in terms of WAD
253+
function _getReserveValueInBaseCurrency(
254+
uint256 assetId,
255+
uint256 amount
256+
) internal view returns (uint256) {
257+
return
258+
(amount * oracle.getAssetPrice(assetId) * WadRayMath.WAD) /
259+
(10 ** hub.getAssetConfig(assetId).decimals);
260+
}
261+
262+
function _calculateExpectedUserRP(address user, ISpoke spoke) internal view returns (uint256) {
263+
uint256 assetId;
264+
uint256 totalDebt;
265+
uint256 suppliedReservesCount;
266+
uint256 userRP;
267+
DataTypes.UserPosition memory userPosition;
268+
269+
// Find all reserves user has supplied, adding up total debt
270+
for (uint256 reserveId; reserveId < spoke.reserveCount(); ++reserveId) {
271+
if (spoke.getUsingAsCollateral(reserveId, user)) {
272+
++suppliedReservesCount;
273+
}
274+
(assetId, ) = getAssetByReserveId(spoke, reserveId);
275+
totalDebt += _getReserveValueInBaseCurrency(
276+
assetId,
277+
spoke.getUserCumulativeDebt(reserveId, user)
278+
);
279+
}
280+
281+
if (totalDebt == 0) {
282+
return 0;
283+
}
284+
285+
// Gather up list of reserves as collateral to sort by LP
286+
KeyValueListInMemory.List memory reserveLP = KeyValueListInMemory.init(suppliedReservesCount);
287+
uint256 idx = 0;
288+
for (uint256 reserveId; reserveId < spoke.reserveCount(); ++reserveId) {
289+
if (spoke.getUsingAsCollateral(reserveId, user)) {
290+
reserveLP.add(idx, spoke.getLiquidityPremium(reserveId), reserveId);
291+
++idx;
292+
}
293+
}
294+
295+
// Sort supplied reserves by LP
296+
reserveLP.sortByKey();
297+
298+
// While user's normalized debt amount is non-zero, iterate through supplied reserves, and add up LP
299+
idx = 0;
300+
uint256 originalTotalDebt = totalDebt;
301+
while (totalDebt > 0) {
302+
(uint256 lp, uint256 reserveId) = reserveLP.get(idx);
303+
userPosition = getUserInfo(spoke, user, reserveId);
304+
(assetId, ) = getAssetByReserveId(spoke, reserveId);
305+
uint256 supplyAmount = _getReserveValueInBaseCurrency(
306+
assetId,
307+
hub.convertToAssets(assetId, userPosition.suppliedShares)
308+
);
309+
310+
if (supplyAmount >= totalDebt) {
311+
userRP += totalDebt * lp;
312+
break;
313+
} else {
314+
userRP += supplyAmount * lp;
315+
totalDebt -= supplyAmount;
316+
}
317+
318+
++idx;
319+
}
320+
321+
return userRP / originalTotalDebt;
322+
}
249323
}

0 commit comments

Comments
 (0)