Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add validation changes and some test helper cleanup #13

Merged
merged 19 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 63 additions & 51 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,51 +1,63 @@
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1040620)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 742686)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 1430357)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidEqualRate() (gas: 150228)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidHigherRate() (gas: 163656)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidLowerRate() (gas: 158542)
TestAstariaV1Pricing:testGetRefinanceConsiderationAsBorrowerZeroRate() (gas: 79849)
TestAstariaV1Pricing:testGetRefinanceConsiderationInsufficientRefinance() (gas: 110588)
TestAstariaV1Pricing:testGetRefinanceConsiderationInvalidRefinance() (gas: 87587)
TestAstariaV1Pricing:testGetRefinanceConsiderationZeroRate() (gas: 142886)
TestAstariaV1Settlement:testGetAuctionStart() (gas: 463886)
TestAstariaV1Settlement:testGetAuctionStartNotStarted() (gas: 463375)
TestAstariaV1Settlement:testGetCurrentAuctionPrice() (gas: 478969)
TestAstariaV1Settlement:testGetCurrentAuctionPriceNoAuction() (gas: 467021)
TestAstariaV1Settlement:testGetSettlementConsiderationDutchAuctionSettlementAbove() (gas: 522337)
TestAstariaV1Settlement:testGetSettlementConsiderationFailedDutchAuction() (gas: 479467)
TestAstariaV1Settlement:testGetSettlementConsiderationLoanNotRecalled() (gas: 469578)
TestAstariaV1Settlement:testV1SettlementHandlerValidate() (gas: 450187)
TestAstariaV1Settlement:testV1SettlementHandlerValidateInvalidHandler() (gas: 450234)
TestAstariaV1Status:testCannotRecallTwice() (gas: 578251)
TestAstariaV1Status:testCannotWithdrawLoanHasNotBeenRefinanced() (gas: 458430)
TestAstariaV1Status:testCannotWithdrawWithdrawDoesNotExist() (gas: 465034)
TestAstariaV1Status:testGenerateRecallConsideration() (gas: 505005)
TestAstariaV1Status:testInvalidRecallInvalidStakeType() (gas: 540401)
TestAstariaV1Status:testInvalidRecallLoanDoesNotExist() (gas: 506233)
TestAstariaV1Status:testIsActive() (gas: 463144)
TestAstariaV1Status:testIsRecalledInsideWindow() (gas: 592786)
TestAstariaV1Status:testIsRecalledOutsideWindow() (gas: 590687)
TestAstariaV1Status:testRecallAndRefinanceInsideWindow() (gas: 754900)
TestAstariaV1Status:testRecallRateActiveRecall() (gas: 579168)
TestAstariaV1Status:testRecallRateEmptyRecall() (gas: 461462)
TestCompoundInterest:testExceedMaxAmount() (gas: 107213)
TestCompoundInterest:testExceedMaxRate() (gas: 115049)
TestCompoundInterest:testMaxAmount() (gas: 105371)
TestCompoundInterest:testMaxRate() (gas: 83992)
TestCompoundInterest:testRateTooLowOne() (gas: 73659)
TestCompoundInterest:testRateTooLowZero() (gas: 68783)
TestV1BorrowerEnforcer:testFuzzRateMethods((uint256,uint256,uint256),uint256) (runs: 256, μ: 1129, ~: 1129)
TestV1BorrowerEnforcer:testRevertLocateCurrentRateAndAmount() (gas: 61208)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerAmountOOB() (gas: 114217)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtBundlesNotSupported() (gas: 87507)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerEnd() (gas: 120964)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerHalfway() (gas: 102738)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateGTCurrent() (gas: 95546)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateLTCurrent() (gas: 102420)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerStart() (gas: 99642)
TestV1LenderEnforcer:testV1LenderEnforcerAdditionalTransfers() (gas: 119290)
TestV1LenderEnforcer:testV1LenderEnforcerAmount() (gas: 118273)
TestV1LenderEnforcer:testV1LenderEnforcerDebtBundlesNotSupported() (gas: 75176)
TestV1LenderEnforcer:testV1LenderEnforcerMatchIdentifier() (gas: 125904)
TestV1LenderEnforcer:testV1LenderEnforcerRate() (gas: 118318)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallAuctionFailLenderClaim() (gas: 653231)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallAuctionFailLenderClaimRandomFulfiller() (gas: 644215)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallBase() (gas: 1042358)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLender() (gas: 666730)
TestAstariaV1Loan:testNewLoanERC721CollateralDefaultTermsRecallLiquidation() (gas: 1438180)
TestAstariaV1Pricing:testGetRefinanceConsiderationAsBorrowerZeroRate() (gas: 79783)
TestAstariaV1Pricing:testGetRefinanceConsiderationInsufficientRefinance() (gas: 111189)
TestAstariaV1Pricing:testGetRefinanceConsiderationInvalidRefinance() (gas: 88304)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidEqualRate() (gas: 149516)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidHigherRate() (gas: 162892)
TestAstariaV1Pricing:testGetRefinanceConsiderationValidLowerRate() (gas: 157711)
TestAstariaV1Pricing:testGetRefinanceConsiderationZeroRate() (gas: 81207)
TestAstariaV1Pricing:testGetRefinanceNewDecimalMismatch() (gas: 81092)
TestAstariaV1Pricing:testV1PricingValidateInvalid() (gas: 69869)
TestAstariaV1Pricing:testV1PricingValidateValid() (gas: 69677)
TestAstariaV1Settlement:testGetAuctionStart() (gas: 468743)
TestAstariaV1Settlement:testGetAuctionStartNotStarted() (gas: 468163)
TestAstariaV1Settlement:testGetCurrentAuctionPrice() (gas: 483815)
TestAstariaV1Settlement:testGetCurrentAuctionPriceNoAuction() (gas: 471816)
TestAstariaV1Settlement:testGetSettlementConsiderationDutchAuctionSettlementAbove() (gas: 524599)
TestAstariaV1Settlement:testGetSettlementConsiderationFailedDutchAuction() (gas: 484272)
TestAstariaV1Settlement:testGetSettlementConsiderationLoanNotRecalled() (gas: 474409)
TestAstariaV1Settlement:testGetSettlementConsiderationLoanRecalledByLender() (gas: 506886)
TestAstariaV1Settlement:testGetSettlementConsiderationNoRecallRate() (gas: 486189)
TestAstariaV1Settlement:testV1SettlementHandlerValidate() (gas: 454714)
TestAstariaV1Settlement:testV1SettlementValidateInvalid() (gas: 62616)
TestAstariaV1Settlement:testV1SettlementValidateValid() (gas: 61991)
TestAstariaV1Status:testCannotRecallTwice() (gas: 581966)
TestAstariaV1Status:testCannotWithdrawLoanHasNotBeenRefinanced() (gas: 463071)
TestAstariaV1Status:testCannotWithdrawWithdrawDoesNotExist() (gas: 469779)
TestAstariaV1Status:testGenerateRecallConsideration() (gas: 508246)
TestAstariaV1Status:testInvalidRecallInvalidStakeType() (gas: 523692)
TestAstariaV1Status:testInvalidRecallLoanDoesNotExist() (gas: 510984)
TestAstariaV1Status:testIsActive() (gas: 467865)
TestAstariaV1Status:testIsRecalledInsideWindow() (gas: 596425)
TestAstariaV1Status:testIsRecalledOutsideWindow() (gas: 594357)
TestAstariaV1Status:testRecallAndRefinanceInsideWindow() (gas: 753362)
TestAstariaV1Status:testRecallRateActiveRecall() (gas: 582949)
TestAstariaV1Status:testRecallRateEmptyRecall() (gas: 466199)
TestAstariaV1Status:testV1StatusValidateInValid() (gas: 69799)
TestAstariaV1Status:testV1StatusValidateValid() (gas: 63252)
TestCompoundInterest:testDecimalsTooHigh() (gas: 3340)
TestCompoundInterest:testDecimalsTooLowZero() (gas: 3391)
TestCompoundInterest:testInterestAccrual() (gas: 67017)
TestCompoundInterest:testMaxAmountDecimals() (gas: 171733)
TestCompoundInterest:testRateExceedsMaxRecallRate() (gas: 3319)
TestCompoundInterest:testRateTooLowOne() (gas: 5105)
TestCompoundInterest:testRateTooLowOneNonWADDecimal() (gas: 6412)
TestCompoundInterest:testRateTooLowZero() (gas: 5126)
TestV1BorrowerEnforcer:testFuzzRateMethods((uint256,uint256,uint256),uint256) (runs: 200, μ: 1107, ~: 1107)
TestV1BorrowerEnforcer:testRevertLocateCurrentRateAndAmount() (gas: 61164)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerAmountOOB() (gas: 114125)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerDebtBundlesNotSupported() (gas: 75113)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerEnd() (gas: 121194)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerHalfway() (gas: 116129)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateGTCurrent() (gas: 95445)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerRateLTCurrent() (gas: 102645)
TestV1BorrowerEnforcer:testV1BorrowerEnforcerStart() (gas: 99757)
TestV1LenderEnforcer:testV1LenderEnforcerAdditionalTransfers() (gas: 119520)
TestV1LenderEnforcer:testV1LenderEnforcerAmount() (gas: 118376)
TestV1LenderEnforcer:testV1LenderEnforcerDebtBundlesNotSupported() (gas: 75149)
TestV1LenderEnforcer:testV1LenderEnforcerMatchIdentifier() (gas: 126134)
TestV1LenderEnforcer:testV1LenderEnforcerRate() (gas: 118310)
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ src = "src"
out = "out"
libs = ["lib"]

[profile.default.fuzz]
include_storage = false
runs = 10000

[fmt]
line_length = 120
tab_width = 4
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
2 changes: 1 addition & 1 deletion lib/starport
Submodule starport updated 42 files
+130 −131 .gas-snapshot
+1 −3 .github/workflows/lint_test.yml
+3 −1 .husky/pre-commit
+2 −2 LICENSE
+4 −0 README.md
+1 −0 foundry.toml
+66 −17 src/BNPLHelper.sol
+212 −206 src/Custodian.sol
+379 −264 src/Starport.sol
+52 −5 src/enforcers/BorrowerEnforcer.sol
+42 −3 src/enforcers/BorrowerEnforcerBNPL.sol
+39 −4 src/enforcers/CaveatEnforcer.sol
+33 −1 src/enforcers/LenderEnforcer.sol
+27 −0 src/interfaces/TokenReceiverInterface.sol
+31 −4 src/lib/PausableNonReentrant.sol
+40 −1 src/lib/RefStarportLib.sol
+140 −78 src/lib/StarportLib.sol
+43 −0 src/lib/Validation.sol
+35 −20 src/originators/Originator.sol
+132 −95 src/originators/StrategistOriginator.sol
+48 −26 src/pricing/BasePricing.sol
+50 −27 src/pricing/Pricing.sol
+52 −25 src/pricing/SimpleInterestPricing.sol
+0 −25 src/scripts/Deploy.sol
+62 −14 src/settlement/DutchAuctionSettlement.sol
+39 −3 src/settlement/FixedTermDutchAuctionSettlement.sol
+53 −32 src/settlement/Settlement.sol
+41 −0 src/status/FixedTermStatus.sol
+32 −20 src/status/Status.sol
+31 −126 test/StarportTest.sol
+271 −73 test/fuzz-testing/TestFuzzStarport.sol
+23 −18 test/integration-testing/TestCaveats.sol
+1 −6 test/integration-testing/TestLoanCombinations.t.sol
+199 −95 test/integration-testing/TestNewLoan.sol
+12 −12 test/integration-testing/TestRepayLoan.sol
+79 −3 test/unit-testing/ModuleTesting.t.sol
+2 −2 test/unit-testing/TestBorrowerEnforcer.sol
+23 −15 test/unit-testing/TestCustodian.sol
+299 −8 test/unit-testing/TestStarport.sol
+2 −2 test/utils/Bound.sol
+5 −5 test/utils/Cast.sol
+1 −1 test/utils/Expect.sol
32 changes: 21 additions & 11 deletions src/enforcers/AstariaV1BorrowerEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ contract AstariaV1BorrowerEnforcer is BorrowerEnforcer {
error DebtBundlesNotSupported();

struct V1BorrowerDetails {
uint256 startTime;
uint256 endTime;
uint256 startBlock;
uint256 endBlock;
uint256 startRate;
uint256 maxAmount;
uint256 minAmount;
BorrowerEnforcer.Details details;
}

/// @notice Validates a loan against a caveat, w/ an inclining rate auction, and a min/max amount
/// @dev Bundle support is not implemented, and will revert
/// @dev The rate in pricing is the endRate.
/// @dev Only viable for use w/ AstariaV1Pricing and AstariaV1Status modules
function validate(
AdditionalTransfer[] calldata additionalTransfers,
Starport.Loan calldata loan,
Expand Down Expand Up @@ -67,32 +71,38 @@ contract AstariaV1BorrowerEnforcer is BorrowerEnforcer {
_validate(additionalTransfers, loan, v1Details.details);
}

/// @notice Calculates the current maximum valid rate of a caveat
function locateCurrentRate(bytes calldata caveatData) external view returns (uint256 currentRate) {
V1BorrowerDetails memory v1Details = abi.decode(caveatData, (V1BorrowerDetails));
return _locateCurrentRate(v1Details);
}

function _locateCurrentRate(V1BorrowerDetails memory v1Details) internal view returns (uint256 currentRate) {
uint256 endRate = AstariaV1Lib.getBasePricingRate(v1Details.details.loan.terms.pricingData);

// if endRate == startRate, or startTime == endTime, or block.timestamp > endTime
// if endRate == startRate, or startBlock == endBlock, or block.number > endBlock
if (
endRate == v1Details.startRate || v1Details.startTime == v1Details.endTime
|| block.timestamp > v1Details.endTime
endRate == v1Details.startRate || v1Details.startBlock == v1Details.endBlock
|| block.number > v1Details.endBlock
) {
return endRate;
}

// Will revert if startTime > endTime
uint256 duration = v1Details.endTime - v1Details.startTime;
// Will revert if startBlock > endBlock
uint256 duration = v1Details.endBlock - v1Details.startBlock;
uint256 elapsed;
uint256 remaining;
unchecked {
// block.timestamp <= endTime && startTime < endTime, can't overflow
elapsed = block.timestamp - v1Details.startTime;
// block.timestamp <= endTime, can't underflow
// block.number <= endBlock && startBlock < endBlock, can't overflow
elapsed = block.number - v1Details.startBlock;
// block.number <= endBlock, can't underflow
remaining = duration - elapsed;
}

// Calculate rate with a linear growth
// Weight startRate by the remaining time, and endRate by the elapsed time
uint256 totalBeforeDivision = (v1Details.startRate * remaining) + (endRate * elapsed);
assembly {
assembly ("memory-safe") {
currentRate := div(totalBeforeDivision, duration)
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/enforcers/AstariaV1LenderEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ contract AstariaV1LenderEnforcer is LenderEnforcer {
LenderEnforcer.Details details;
}

/// @notice Validates a loan against a caveat, w/ a minimum rate and a maximum amount
/// @dev Bundle support is not implemented, and will revert
/// @dev matchIdentifier = false will allow the loan to have a different identifier than the caveat
/// @dev Only viable for use w/ AstariaV1Pricing and AstariaV1Status modules
function validate(
AdditionalTransfer[] calldata additionalTransfers,
Starport.Loan calldata loan,
Expand All @@ -36,7 +40,6 @@ contract AstariaV1LenderEnforcer is LenderEnforcer {
Starport.Terms calldata loanTerms = loan.terms;
uint256 loanRate = abi.decode(loanTerms.pricingData, (BasePricing.Details)).rate;
uint256 loanAmount = loan.debt[0].amount;

AstariaV1Lib.validateCompoundInterest(
loanAmount,
loanRate,
Expand Down
11 changes: 5 additions & 6 deletions src/lib/AstariaV1Lib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,33 +43,32 @@ library AstariaV1Lib {

// calculate interest for 1 second of time
// loan must produce 1 wei of interest per 1 second of time
uint256 interest = calculateCompoundInterest(1, amount, rate, decimals);
if (interest == 0) {
if (calculateCompoundInterest(1, amount, rate, decimals) == 0) {
// interest does not accrue at least 1 wei per second
revert InterestAccrualRoundingMinimum();
}
}

function getBaseRecallRecallMax(bytes memory statusData) internal pure returns (uint256 recallMax) {
assembly {
assembly ("memory-safe") {
recallMax := mload(add(0x80, statusData))
}
}

function getBasePricingDecimals(bytes memory pricingData) internal pure returns (uint256 decimals) {
assembly {
assembly ("memory-safe") {
decimals := mload(add(0x60, pricingData))
}
}

function getBasePricingRate(bytes memory pricingData) internal pure returns (uint256 rate) {
assembly {
assembly ("memory-safe") {
rate := mload(add(0x20, pricingData))
}
}

function setBasePricingRate(bytes memory pricingData, uint256 newRate) internal pure {
assembly {
assembly ("memory-safe") {
mstore(add(0x20, pricingData), newRate)
}
}
Expand Down
31 changes: 20 additions & 11 deletions src/pricing/AstariaV1Pricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {AstariaV1Lib} from "v1-core/lib/AstariaV1Lib.sol";

import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol";
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";
import {Validation} from "starport-core/lib/Validation.sol";

contract AstariaV1Pricing is CompoundInterestPricing {
using FixedPointMathLib for uint256;
Expand All @@ -24,6 +25,7 @@ contract AstariaV1Pricing is CompoundInterestPricing {

error InsufficientRefinance();

// @inheritdoc Pricing
function getRefinanceConsideration(Starport.Loan calldata loan, bytes calldata newPricingData, address fulfiller)
external
view
Expand All @@ -40,18 +42,17 @@ contract AstariaV1Pricing is CompoundInterestPricing {
// check if a recall is occuring
AstariaV1Status status = AstariaV1Status(loan.terms.status);

if (!status.isRecalled(loan)) {
Details memory newDetails = abi.decode(newPricingData, (Details));
Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details));
if (!status.isRecalled(loan) || newDetails.decimals != oldDetails.decimals || newDetails.rate == 0) {
revert InvalidRefinance();
}
Details memory newDetails = abi.decode(newPricingData, (Details));
uint256 rate = status.getRecallRate(loan);
// offered loan did not meet the terms of the recall auction
if (newDetails.rate > rate) {
revert InsufficientRefinance();
}

Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details));

uint256 proportion;
address payable receiver = payable(loan.issuer);
uint256 loanId = loan.getId();
Expand All @@ -72,13 +73,21 @@ contract AstariaV1Pricing is CompoundInterestPricing {
(repayConsideration, carryConsideration) = getPaymentConsideration(loan);
}

function validate(Starport.Loan calldata loan) external pure virtual override returns (bytes4) {
uint256 loanRate = abi.decode(loan.terms.pricingData, (BasePricing.Details)).rate;
uint256 loanAmount = loan.debt[0].amount;
uint256 recallMax = AstariaV1Lib.getBaseRecallRecallMax(loan.terms.statusData);
uint256 decimals = AstariaV1Lib.getBasePricingDecimals(loan.terms.pricingData);
// @inheritdoc Validation
function validate(Starport.Loan calldata loan) external view virtual override returns (bytes4 selector) {
if (msg.sender == address(this)) {
uint256 loanRate = abi.decode(loan.terms.pricingData, (BasePricing.Details)).rate;
uint256 loanAmount = loan.debt[0].amount;
uint256 recallMax = AstariaV1Lib.getBaseRecallRecallMax(loan.terms.statusData);
uint256 decimals = AstariaV1Lib.getBasePricingDecimals(loan.terms.pricingData);

AstariaV1Lib.validateCompoundInterest(loanAmount, loanRate, recallMax, decimals);
return Pricing.validate.selector;
AstariaV1Lib.validateCompoundInterest(loanAmount, loanRate, recallMax, decimals);
} else {
try Validation(address(this)).validate(loan) {
selector = Validation.validate.selector;
} catch {
selector = bytes4(0xFFFFFFFF);
}
}
}
}
2 changes: 1 addition & 1 deletion src/pricing/BaseRecallPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol";
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";

abstract contract BaseRecallPricing is BasePricing {
// @inheritdoc Pricing
function getRefinanceConsideration(Starport.Loan calldata loan, bytes memory newPricingData, address fulfiller)
external
view
Expand All @@ -28,7 +29,6 @@ abstract contract BaseRecallPricing is BasePricing {
Details memory newDetails = abi.decode(newPricingData, (Details));
bool isRecalled = BaseStatus(loan.terms.status).isRecalled(loan);

//todo: figure out the proper flow for here
if ((isRecalled && newDetails.rate >= oldDetails.rate) || (newDetails.rate < oldDetails.rate)) {
(repayConsideration, carryConsideration) = getPaymentConsideration(loan);
recallConsideration = new AdditionalTransfer[](0);
Expand Down
1 change: 1 addition & 0 deletions src/pricing/CompoundInterestPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {BaseRecallPricing} from "v1-core/pricing/BaseRecallPricing.sol";
import {AstariaV1Lib} from "v1-core/lib/AstariaV1Lib.sol";

abstract contract CompoundInterestPricing is BaseRecallPricing {
// @inheritdoc BasePricing
function calculateInterest(uint256 delta_t, uint256 amount, uint256 rate, uint256 decimals)
public
pure
Expand Down
Loading