diff --git a/lib/starport b/lib/starport index a4cd30b..b64d925 160000 --- a/lib/starport +++ b/lib/starport @@ -1 +1 @@ -Subproject commit a4cd30b5cc8bbb8fdc7c96040f292ca417a5b574 +Subproject commit b64d9252360b7c5027cd55c076a650079efb65c6 diff --git a/src/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index 7d42982..5e0378d 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -42,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(); @@ -75,13 +74,20 @@ contract AstariaV1Pricing is CompoundInterestPricing { } // @inheritdoc Validation - 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); + 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 Validation.validate.selector; + AstariaV1Lib.validateCompoundInterest(loanAmount, loanRate, recallMax, decimals); + } else { + try Validation(address(this)).validate(loan) { + selector = Validation.validate.selector; + } catch { + selector = bytes4(0xFFFFFFFF); + } + } } } diff --git a/src/settlement/AstariaV1Settlement.sol b/src/settlement/AstariaV1Settlement.sol index 97acd0e..11639ba 100644 --- a/src/settlement/AstariaV1Settlement.sol +++ b/src/settlement/AstariaV1Settlement.sol @@ -190,9 +190,6 @@ contract AstariaV1Settlement is DutchAuctionSettlement { // @inheritdoc Validation function validate(Starport.Loan calldata loan) external view virtual override returns (bytes4) { - if (loan.terms.settlement != address(this)) { - revert InvalidHandler(); - } Details memory details = abi.decode(loan.terms.settlementData, (Details)); // Will revert if this fails return (details.startingPrice > details.endingPrice) ? Validation.validate.selector : bytes4(0xFFFFFFFF); } diff --git a/src/status/AstariaV1Status.sol b/src/status/AstariaV1Status.sol index 6067887..9ee45a3 100644 --- a/src/status/AstariaV1Status.sol +++ b/src/status/AstariaV1Status.sol @@ -37,10 +37,7 @@ contract AstariaV1Status is BaseStatus, BaseRecall { Details memory details = abi.decode(loan.terms.statusData, (Details)); BasePricing.Details memory pDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); bool valid = true; - if ( - details.recallerRewardRatio > 10 ** pDetails.decimals || details.recallMax > 10 * 10 ** pDetails.decimals - || details.honeymoon == 0 - ) { + if (details.recallerRewardRatio > 10 ** pDetails.decimals || details.recallMax > 10 * 10 ** pDetails.decimals) { valid = false; } return valid ? Validation.validate.selector : bytes4(0xFFFFFFFF); diff --git a/test/TestV1Pricing.sol b/test/TestV1Pricing.sol index 8a69b55..410562d 100644 --- a/test/TestV1Pricing.sol +++ b/test/TestV1Pricing.sol @@ -15,6 +15,7 @@ import {AstariaV1Lib} from "v1-core/lib/AstariaV1Lib.sol"; import {DeepEq} from "starport-test/utils/DeepEq.sol"; import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import {Validation} from "starport-core/lib/Validation.sol"; contract TestAstariaV1Pricing is AstariaV1Test, DeepEq { using Cast for *; @@ -96,7 +97,6 @@ contract TestAstariaV1Pricing is AstariaV1Test, DeepEq { _deepEq(expectedAdditionalTransfers, additionalTransfers); } - //TODO: is 0 rate allowed? function testGetRefinanceConsiderationZeroRate() public { Starport.Loan memory loan = generateDefaultLoanTerms(); loan.start = uint256(1); @@ -104,42 +104,26 @@ contract TestAstariaV1Pricing is AstariaV1Test, DeepEq { vm.warp(2); BasePricing.Details memory baseDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); bytes memory newPricingData = abi.encode(BasePricing.Details({carryRate: 0, rate: 0, decimals: 18})); - BaseRecall.Details memory statusDetails = abi.decode(loan.terms.statusData, (BaseRecall.Details)); - uint256 proportion = 1e18 - (baseDetails.rate - 0).divWad(baseDetails.rate); vm.mockCall( loan.terms.status, abi.encodeWithSelector(AstariaV1Status.isRecalled.selector, loan), abi.encode(true) ); - SpentItem[] memory expectedConsideration = new SpentItem[](1); - expectedConsideration[0] = SpentItem({ - itemType: loan.debt[0].itemType, - amount: loan.debt[0].amount - + AstariaV1Lib.calculateCompoundInterest( - block.timestamp - loan.start, loan.debt[0].amount, baseDetails.rate, 18 - ), - identifier: loan.debt[0].identifier, - token: loan.debt[0].token - }); - SpentItem[] memory expectedCarryConsideration = new SpentItem[](0); - AdditionalTransfer[] memory expectedAdditionalTransfers = new AdditionalTransfer[](1); - expectedAdditionalTransfers[0] = AdditionalTransfer({ - identifier: loan.debt[0].identifier, - itemType: loan.debt[0].itemType, - token: loan.debt[0].token, - amount: AstariaV1Lib.calculateCompoundInterest( - statusDetails.recallStakeDuration, loan.debt[0].amount, baseDetails.rate, 18 - ).mulWad(proportion), - to: loan.issuer, - from: address(this) - }); + vm.expectRevert(Pricing.InvalidRefinance.selector); + Pricing(loan.terms.pricing).getRefinanceConsideration(loan, newPricingData, address(this)); + } - ( - SpentItem[] memory consideration, - SpentItem[] memory carryConsideration, - AdditionalTransfer[] memory additionalTransfers - ) = Pricing(loan.terms.pricing).getRefinanceConsideration(loan, newPricingData, address(this)); - _deepEq(consideration, expectedConsideration); - _deepEq(carryConsideration, expectedCarryConsideration); - _deepEq(expectedAdditionalTransfers, additionalTransfers); + function testGetRefinanceNewDecimalMismatch() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + loan.start = uint256(1); + loan.originator = address(this); + vm.warp(2); + BasePricing.Details memory baseDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + bytes memory newPricingData = + abi.encode(BasePricing.Details({carryRate: 0, rate: baseDetails.rate, decimals: 6})); + vm.mockCall( + loan.terms.status, abi.encodeWithSelector(AstariaV1Status.isRecalled.selector, loan), abi.encode(true) + ); + vm.expectRevert(Pricing.InvalidRefinance.selector); + Pricing(loan.terms.pricing).getRefinanceConsideration(loan, newPricingData, address(this)); } function testGetRefinanceConsiderationValidEqualRate() public { @@ -304,4 +288,18 @@ contract TestAstariaV1Pricing is AstariaV1Test, DeepEq { _deepEq(carryConsideration, expectedCarryConsideration); _deepEq(expectedAdditionalTransfers, additionalTransfers); } + + function testV1PricingValidateValid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + assert(Validation(loan.terms.pricing).validate(loan) == Validation.validate.selector); + } + + function testV1PricingValidateInvalid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + BasePricing.Details memory baseDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + baseDetails.rate = 0; + + loan.terms.pricingData = abi.encode(baseDetails); + assert(Validation(loan.terms.pricing).validate(loan) == bytes4(0xFFFFFFFF)); + } } diff --git a/test/TestV1Settlement.sol b/test/TestV1Settlement.sol index b3fb46e..ef55dd2 100644 --- a/test/TestV1Settlement.sol +++ b/test/TestV1Settlement.sol @@ -318,4 +318,19 @@ contract TestAstariaV1Settlement is AstariaV1Test, DeepEq { vm.expectRevert(abi.encodeWithSelector(AstariaV1Settlement.InvalidHandler.selector)); AstariaV1Settlement(settlement).validate(loan); } + + function testV1SettlementValidateValid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + assert(Validation(loan.terms.settlement).validate(loan) == Validation.validate.selector); + } + + function testV1SettlementValidateInvalid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + DutchAuctionSettlement.Details memory details = + abi.decode(loan.terms.settlementData, (DutchAuctionSettlement.Details)); + details.endingPrice = 10; + details.startingPrice = 1; + loan.terms.settlementData = abi.encode(details); + assert(Validation(loan.terms.settlement).validate(loan) == bytes4(0xFFFFFFFF)); + } } diff --git a/test/TestV1Status.sol b/test/TestV1Status.sol index 6f51810..1277af4 100644 --- a/test/TestV1Status.sol +++ b/test/TestV1Status.sol @@ -12,6 +12,7 @@ import {StarportLib, Actions} from "starport-core/lib/StarportLib.sol"; import {DeepEq} from "starport-test/utils/DeepEq.sol"; import {SpentItemLib} from "seaport-sol/src/lib/SpentItemLib.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; +import {Validation} from "starport-core/lib/Validation.sol"; contract TestAstariaV1Status is AstariaV1Test, DeepEq { using Cast for *; @@ -314,6 +315,26 @@ contract TestAstariaV1Status is AstariaV1Test, DeepEq { vm.expectRevert(abi.encodeWithSelector(BaseRecall.LoanHasNotBeenRefinanced.selector)); AstariaV1Status(loan.terms.status).withdraw(loan, payable(address(this))); } + + function testV1StatusValidateValid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + assert(Validation(loan.terms.status).validate(loan) == Validation.validate.selector); + } + + //if (details.recallerRewardRatio > 10 ** pDetails.decimals || details.recallMax > 10 * 10 ** pDetails.decimals) { + function testV1StatusValidateInValid() public { + Starport.Loan memory loan = generateDefaultLoanTerms(); + bytes memory defaultDetailsData = loan.terms.statusData; + BaseRecall.Details memory details = abi.decode(loan.terms.statusData, (BaseRecall.Details)); + details.recallerRewardRatio = 10 ** 19; + loan.terms.statusData = abi.encode(details); + assert(Validation(loan.terms.status).validate(loan) == bytes4(0xFFFFFFFF)); + details = abi.decode(defaultDetailsData, (BaseRecall.Details)); + details.recallMax = 1000 ** 19; + loan.terms.statusData = abi.encode(details); + assert(Validation(loan.terms.status).validate(loan) == bytes4(0xFFFFFFFF)); + } + // //TODO: this needs to be done because withdraw is being looked at // function testRecallWithdraw() public { // Starport.Terms memory terms = Starport.Terms({ diff --git a/test/fuzz-testing/TestFuzzV1.sol b/test/fuzz-testing/TestFuzzV1.sol index 501fabd..3b53156 100644 --- a/test/fuzz-testing/TestFuzzV1.sol +++ b/test/fuzz-testing/TestFuzzV1.sol @@ -166,6 +166,23 @@ contract TestFuzzV1 is AstariaV1Test, TestFuzzStarport { skip(_boundMax(1, uint256(3 * 365 days))); } + function willArithmeticOverflow(Starport.Loan memory loan) internal view virtual override returns (bool) { + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + try Pricing(loan.terms.pricing).getPaymentConsideration(loan) returns ( + SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration + ) { + unchecked { + uint256 newSupply = erc20s[0].totalSupply() + repayConsideration[0].amount; + if (newSupply < erc20s[0].totalSupply() || newSupply < repayConsideration[0].amount) { + return true; + } + } + return false; + } catch { + return true; + } + } + function _skipToSettlement(Starport.Loan memory goodLoan) internal virtual override { BasePricing.Details memory pricingDetails = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details)); BaseRecall.Details memory details = abi.decode(goodLoan.terms.statusData, (BaseRecall.Details)); @@ -263,9 +280,10 @@ contract TestFuzzV1 is AstariaV1Test, TestFuzzStarport { SpentItem[] memory carryPayment, bytes memory pricingData ) internal virtual returns (CaveatEnforcer.SignedCaveats memory signedCaveats) { - LenderEnforcer.Details memory details = LenderEnforcer.Details({ - loan: SP.applyRefinanceConsiderationToLoan(goodLoan, considerationPayment, carryPayment, pricingData) - }); + Starport.Loan memory refiLoan = loanCopy(goodLoan); + refiLoan.terms.pricingData = pricingData; + refiLoan.debt = SP.applyRefinanceConsiderationToLoan(considerationPayment, carryPayment); + LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan}); AstariaV1LenderEnforcer.V1LenderDetails memory lenderDetails = AstariaV1LenderEnforcer.V1LenderDetails({matchIdentifier: true, details: details});