diff --git a/src/Custodian.sol b/src/Custodian.sol index 99f49a04..55492362 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -412,14 +412,12 @@ contract Custodian is ERC721, ContractOffererInterface { */ function _postSettlementExecute(Starport.Loan memory loan, address fulfiller) internal virtual { _beforeSettlementHandlerHook(loan); - if ( - Settlement(loan.terms.settlement).postSettlement{gas: 100_000}(loan, fulfiller) - != Settlement.postSettlement.selector - ) { + if (Settlement(loan.terms.settlement).postSettlement(loan, fulfiller) != Settlement.postSettlement.selector) { revert InvalidPostSettlement(); } _afterSettlementHandlerHook(loan); } + /** * @dev settle the loan with the LoanManager * @@ -429,10 +427,7 @@ contract Custodian is ERC721, ContractOffererInterface { function _postRepaymentExecute(Starport.Loan memory loan, address fulfiller) internal virtual { _beforeSettlementHandlerHook(loan); - if ( - Settlement(loan.terms.settlement).postRepayment{gas: 100_000}(loan, fulfiller) - != Settlement.postRepayment.selector - ) { + if (Settlement(loan.terms.settlement).postRepayment(loan, fulfiller) != Settlement.postRepayment.selector) { revert InvalidPostRepayment(); } _afterSettlementHandlerHook(loan); diff --git a/src/Starport.sol b/src/Starport.sol index 681303f6..4038675e 100644 --- a/src/Starport.sol +++ b/src/Starport.sol @@ -36,6 +36,7 @@ import {CaveatEnforcer} from "starport-core/enforcers/CaveatEnforcer.sol"; import {Ownable} from "solady/src/auth/Ownable.sol"; import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; import {PausableNonReentrant} from "starport-core/lib/PausableNonReentrant.sol"; +import {Settlement} from "starport-core/settlement/Settlement.sol"; contract Starport is ERC721, PausableNonReentrant { using FixedPointMathLib for uint256; @@ -121,6 +122,7 @@ contract Starport is ERC721, PausableNonReentrant { error UnauthorizedAdditionalTransferIncluded(); error InvalidCaveatSigner(); error MalformedRefinance(); + error InvalidPostRepayment(); constructor(ConsiderationInterface seaport_) { address custodian = address(new Custodian(this, seaport_)); @@ -227,6 +229,7 @@ contract Starport is ERC721, PausableNonReentrant { ) = Pricing(loan.terms.pricing).getRefinanceConsideration(loan, pricingData, msg.sender); _settle(loan); + _postRepaymentExecute(loan, msg.sender); loan = applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData); StarportLib.transferSpentItems(considerationPayment, lender, loan.issuer, false); @@ -251,6 +254,18 @@ contract Starport is ERC721, PausableNonReentrant { _issueLoan(loan); } + /** + * @dev settle the loan with the LoanManager + * + * @param loan The the loan that is settled + * @param fulfiller The address executing seaport + */ + function _postRepaymentExecute(Starport.Loan memory loan, address fulfiller) internal virtual { + if (Settlement(loan.terms.settlement).postRepayment(loan, fulfiller) != Settlement.postRepayment.selector) { + revert InvalidPostRepayment(); + } + } + function applyRefinanceConsiderationToLoan( Starport.Loan memory loan, SpentItem[] memory considerationPayment, diff --git a/src/settlement/AstariaV1Settlement.sol b/src/settlement/AstariaV1Settlement.sol index f0387af6..304a9545 100644 --- a/src/settlement/AstariaV1Settlement.sol +++ b/src/settlement/AstariaV1Settlement.sol @@ -150,15 +150,20 @@ contract AstariaV1Settlement is DutchAuctionSettlement { override returns (bytes4) { - // TODO: do we need the commented out code if we dont care about reverts anyways, seems like extra gas - // (address recaller, uint64 recallStart) = BaseRecall(loan.terms.status).recalls(loan.getId()); - // if (recallStart != 0 || recaller != address(0)) { - //we dont wanna revert if theres ever a halt in the underlying call, settlement must complete - loan.terms.status.call(abi.encodeWithSelector(BaseRecall.withdraw.selector, loan, fulfiller)); - // } + _executeWithdraw(loan, fulfiller); return Settlement.postSettlement.selector; } + function postRepayment(Starport.Loan calldata loan, address fulfiller) external virtual override returns (bytes4) { + _executeWithdraw(loan, fulfiller); + + return Settlement.postRepayment.selector; + } + + function _executeWithdraw(Starport.Loan calldata loan, address fulfiller) internal { + loan.terms.status.call(abi.encodeWithSelector(BaseRecall.withdraw.selector, loan, fulfiller)); + } + function validate(Starport.Loan calldata loan) external view virtual override returns (bool) { if (loan.terms.settlement != address(this)) { revert InvalidHandler(); diff --git a/src/settlement/DutchAuctionSettlement.sol b/src/settlement/DutchAuctionSettlement.sol index 4a31e126..f4041d77 100644 --- a/src/settlement/DutchAuctionSettlement.sol +++ b/src/settlement/DutchAuctionSettlement.sol @@ -16,9 +16,7 @@ import {Starport, Settlement} from "starport-core/settlement/Settlement.sol"; import {BasePricing} from "starport-core/pricing/BasePricing.sol"; abstract contract DutchAuctionSettlement is Settlement, AmountDeriver { - constructor(Starport SP_) Settlement(SP_) { - SP = SP_; - } + constructor(Starport SP_) Settlement(SP_) {} using FixedPointMathLib for uint256; diff --git a/src/settlement/Settlement.sol b/src/settlement/Settlement.sol index 81d336f1..64d609cb 100644 --- a/src/settlement/Settlement.sol +++ b/src/settlement/Settlement.sol @@ -26,7 +26,7 @@ import {SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStruct import {TokenReceiverInterface} from "starport-core/interfaces/TokenReceiverInterface.sol"; abstract contract Settlement is TokenReceiverInterface { - Starport SP; + Starport public immutable SP; constructor(Starport SP_) { SP = SP_; diff --git a/test/integration-testing/TestAstariaV1Loan.sol b/test/integration-testing/TestAstariaV1Loan.sol index 90b4c217..9d370873 100644 --- a/test/integration-testing/TestAstariaV1Loan.sol +++ b/test/integration-testing/TestAstariaV1Loan.sol @@ -152,7 +152,7 @@ contract TestAstariaV1Loan is AstariaV1Test { erc20s[0].approve(address(SP), stake); refinanceLoan(loan, pricingData, address(this), refinancerCaveat, refinancer.addr); - console.log("here2"); + console.log("here2", stake); } uint256 delta_t = block.timestamp - loan.start; @@ -182,19 +182,17 @@ contract TestAstariaV1Loan is AstariaV1Test { ); { - uint256 oldOriginatorAfter = erc20s[0].balanceOf(loan.originator); assertEq( - oldOriginatorAfter, + erc20s[0].balanceOf(loan.originator), oldOriginatorBefore + interest.mulWad(pricingDetails.carryRate), "Carry payment to old originator calculated incorrectly" ); } { - uint256 newFullfillerAfter = erc20s[0].balanceOf(address(this)); assertEq( - newFullfillerAfter, - newFullfillerBefore - stake, + erc20s[0].balanceOf(address(this)), + newFullfillerBefore, "New fulfiller did not repay recaller stake correctly" ); } @@ -205,22 +203,14 @@ contract TestAstariaV1Loan is AstariaV1Test { } } { - uint256 withdrawerBalanceBefore = erc20s[0].balanceOf(address(this)); uint256 recallContractBalanceBefore = erc20s[0].balanceOf(address(status)); BaseRecall recallContract = BaseRecall(address(status)); // attempt a withdraw after the loan has been successfully refinanced - recallContract.withdraw(loan, payable(address(this))); - uint256 withdrawerBalanceAfter = erc20s[0].balanceOf(address(this)); + // recallContract.withdraw(loan, payable(address(this))); + uint256 recallContractBalanceAfter = erc20s[0].balanceOf(address(status)); - assertEq( - withdrawerBalanceBefore + stake, withdrawerBalanceAfter, "Withdrawer did not recover stake as expected" - ); - assertEq( - recallContractBalanceBefore - stake, - recallContractBalanceAfter, - "BaseRecall did not return the stake as expected" - ); + assertEq(recallContractBalanceAfter, uint256(0), "BaseRecall did get emptied as expected"); } } diff --git a/test/unit-testing/TestV1Status.sol b/test/unit-testing/TestV1Status.sol index 7ba443c6..08ae032b 100644 --- a/test/unit-testing/TestV1Status.sol +++ b/test/unit-testing/TestV1Status.sol @@ -61,6 +61,42 @@ contract TestAstariaV1Status is AstariaV1Test, DeepEq { assert(AstariaV1Status(loan.terms.status).isRecalled(loan)); } + function testRecallAndRefinanceInsideWindow() public { + Starport.Terms memory terms = Starport.Terms({ + status: address(status), + settlement: address(settlement), + pricing: address(pricing), + pricingData: defaultPricingData, + settlementData: defaultSettlementData, + statusData: defaultStatusData + }); + Starport.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms}); + uint256 loanId = loan.getId(); + + BaseRecall.Details memory details = abi.decode(loan.terms.statusData, (BaseRecall.Details)); + + erc20s[0].mint(address(this), 10e18); + erc20s[0].approve(loan.terms.status, 10e18); + + skip(details.honeymoon); + vm.expectEmit(); + emit Recalled(loanId, address(this), block.timestamp + details.recallWindow); + AstariaV1Status(loan.terms.status).recall(loan); + (address recaller, uint64 recallStart) = AstariaV1Status(loan.terms.status).recalls(loanId); + skip(details.recallWindow - 1); + address newLender = address(55); + + BasePricing.Details memory newPricingData = abi.decode(defaultPricingData, (BasePricing.Details)); + newPricingData.rate = newPricingData.rate * 2; + + vm.startPrank(newLender); + erc20s[0].mint(newLender, 10e18); + erc20s[0].approve(address(SP), 10e18); + SP.refinance(newLender, _emptyCaveat(), loan, abi.encode(newPricingData)); + assert(erc20s[0].balanceOf(address(loan.terms.status)) == 0); + } + function testInvalidRecallLoanDoesNotExist() public { Starport.Terms memory terms = Starport.Terms({ status: address(status),