Skip to content

Commit

Permalink
additional tests for v1 hook as well as some cleanup/fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
androolloyd committed Nov 2, 2023
1 parent 3f3ec62 commit 832c0a7
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 26 deletions.
57 changes: 57 additions & 0 deletions mermaid/origination.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
sequenceDiagram
title Starport Origination Sequence Diagram
participant F as Fulfiller
participant L as Lender
participant B as Borrower


F->>LoanManager: originate

opt F is not Borrower
loop 1->n
LoanManager->>CaveatEnforcer: validate
end
end
opt F is not Lender
loop 1->n
LoanManager->>CaveatEnforcer: validate
end
end
loop Transfer 1->n collateral items
B->>Custodian: Move Collateral to Custodian
end
opt Custodian is not default
LoanManager->>Custodian: custody (optional)
end

alt Fees Disabled
loop Transfer 1->n debt items
L->>B: Move debt to borrower
end
else Fees Enabled
LoanManager->>LoanManager: compute feeRake
opt FeeItems length > 0
loop Transfer 1->n fee items
L->>FeeRecipient: Move Fee to FeeRecipient
end
end
loop Transfer 1->n debt items
L->>B: Move debt to Borrower
end
end

opt AdditionalTransferItems length > 0
loop 1->n
alt From is Borrower
B->>AdditionalTransferRecipient: AdditionalTransferItem from Borrower => AdditionalTransferRecipient
else From is Lender
L->>AdditionalTransferRecipient: AdditionalTransferItem from Lender => AdditionalTransferRecipient
else From is F
F->>AdditionalTransferRecipient: AdditionalTransferItem from Fulfiller => AdditionalTransferRecipient
end
end
end

opt Lender is contract
LoanManager->>L: onERC721Received
end
1 change: 1 addition & 0 deletions mermaid/origination.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "starlite",
"dependencies": {
"@chainlink/contracts": "^0.6.1",
"@mermaid-js/mermaid-cli": "^10.6.0",
"chai": "^4.3.7",
"global": "^4.4.0",
"husky": "^8.0.3",
"mocha": "^10.2.0",
"prettier": "^2.8.8",
Expand Down
29 changes: 15 additions & 14 deletions src/hooks/BaseRecall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ abstract contract BaseRecall {
error LoanHasNotBeenRefinanced();
error WithdrawDoesNotExist();
error InvalidItemType();
error RecallAlreadyExists();

mapping(uint256 => Recall) public recalls;

Expand Down Expand Up @@ -99,26 +100,22 @@ abstract contract BaseRecall {
}

if (loan.issuer != msg.sender && loan.borrower != msg.sender) {
// (,, address conduitController) = LM.seaport().information();
// validate that the provided conduit is owned by the msg.sender
// if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) {
// revert InvalidConduit();
// }
AdditionalTransfer[] memory recallConsideration = _generateRecallConsideration(
loan, 0, details.recallStakeDuration, 1e18, msg.sender, payable(address(this))
);
StarPortLib.transferAdditionalTransfers(recallConsideration);
}
// get conduitController

bytes memory encodedLoan = abi.encode(loan);

uint256 loanId = uint256(keccak256(encodedLoan));
uint256 loanId = loan.getId();

if (!LM.active(loanId)) revert LoanDoesNotExist();
if (!LM.active(loanId)) {
revert LoanDoesNotExist();
}

if (recalls[loanId].start > 0) {
revert RecallAlreadyExists();
}
recalls[loanId] = Recall(payable(msg.sender), uint64(block.timestamp));
emit Recalled(loanId, msg.sender, loan.start + details.recallWindow);
emit Recalled(loanId, msg.sender, block.timestamp + details.recallWindow);
}

// transfers all stake to anyone who asks after the LM token is burned
Expand All @@ -128,7 +125,9 @@ abstract contract BaseRecall {
uint256 loanId = uint256(keccak256(encodedLoan));

// loan has not been refinanced, loan is still active. LM.tokenId changes on refinance
if (!LM.inactive(loanId)) revert LoanHasNotBeenRefinanced();
if (!LM.inactive(loanId)) {
revert LoanHasNotBeenRefinanced();
}

Recall storage recall = recalls[loanId];
// ensure that a recall exists for the provided tokenId, ensure that the recall
Expand All @@ -143,7 +142,9 @@ abstract contract BaseRecall {
recall.start = 0;

for (uint256 i; i < recallConsideration.length;) {
if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType();
if (loan.debt[i].itemType != ItemType.ERC20) {
revert InvalidItemType();
}

ERC20(loan.debt[i].token).transfer(receiver, recallConsideration[i].amount);

Expand Down
4 changes: 3 additions & 1 deletion src/pricing/AstariaV1Pricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ contract AstariaV1Pricing is CompoundInterestPricing {
if (hook.isRecalled(loan)) {
uint256 rate = hook.getRecallRate(loan);
// offered loan did not meet the terms of the recall auction
if (newDetails.rate > rate) revert InsufficientRefinance();
if (newDetails.rate > rate) {
revert InsufficientRefinance();
}
}
// recall is not occuring
else {
Expand Down
131 changes: 120 additions & 11 deletions test/unit-testing/TestV1Hook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq {
using stdStorage for StdStorage;
using {StarPortLib.getId} for LoanManager.Loan;

event Recalled(uint256 loandId, address recaller, uint256 end);
event Withdraw(uint256 loanId, address withdrawer);

function testIsActive() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
Expand Down Expand Up @@ -49,13 +52,89 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq {
erc20s[0].approve(loan.terms.hook, 10e18);

skip(details.honeymoon);
vm.expectEmit();
emit Recalled(loanId, address(this), block.timestamp + details.recallWindow);
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
(address recaller, uint64 recallStart) = AstariaV1SettlementHook(loan.terms.hook).recalls(loanId);
skip(details.recallWindow - 1);
assert(AstariaV1SettlementHook(loan.terms.hook).isActive(loan));
assert(AstariaV1SettlementHook(loan.terms.hook).isRecalled(loan));
}

function testInvalidRecallLoanDoesNotExist() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
handler: address(handler),
pricing: address(pricing),
pricingData: defaultPricingData,
handlerData: defaultHandlerData,
hookData: defaultHookData
});
LoanManager.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
uint256 loanId = loan.getId();

BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details));

erc20s[0].mint(address(this), 10e18);
erc20s[0].approve(loan.terms.hook, 10e18);

skip(details.honeymoon);
vm.mockCall(address(LM), abi.encodeWithSelector(LM.active.selector, loan.getId()), abi.encode(false));
vm.expectRevert(abi.encodeWithSelector(BaseRecall.LoanDoesNotExist.selector));
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
}

function testInvalidRecallInvalidStakeType() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
handler: address(handler),
pricing: address(pricing),
pricingData: defaultPricingData,
handlerData: defaultHandlerData,
hookData: defaultHookData
});
LoanManager.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
uint256 loanId = loan.getId();

loan.debt[0].itemType = ItemType.ERC721;
loan.debt[0].amount = 1;
BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details));

skip(details.honeymoon);
vm.mockCall(address(LM), abi.encodeWithSelector(LM.active.selector, loan.getId()), abi.encode(true));
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
skip(details.recallWindow);
vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loan.getId()), abi.encode(true));
vm.expectRevert(abi.encodeWithSelector(BaseRecall.InvalidItemType.selector));
AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this)));
}

function testCannotRecallTwice() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
handler: address(handler),
pricing: address(pricing),
pricingData: defaultPricingData,
handlerData: defaultHandlerData,
hookData: defaultHookData
});
LoanManager.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
uint256 loanId = loan.getId();

BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details));

erc20s[0].mint(address(this), 10e18);
erc20s[0].approve(loan.terms.hook, 10e18);

skip(details.honeymoon);
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
vm.expectRevert(abi.encodeWithSelector(BaseRecall.RecallAlreadyExists.selector));
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
}

function testIsRecalledOutsideWindow() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
Expand Down Expand Up @@ -163,8 +242,7 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq {
assertEq(recallRate, computedRecallRate);
}

//TODO: this needs to be done because withdraw is being looked at
function testRecallWithdraw() public {
function testCannotWithdrawWithdrawDoesNotExist() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
handler: address(handler),
Expand All @@ -175,15 +253,46 @@ contract TestAstariaV1Hook is AstariaV1Test, DeepEq {
});
LoanManager.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
uint256 loanId = loan.getId();
BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details));

erc20s[0].mint(address(this), 10e18);
erc20s[0].approve(loan.terms.hook, 10e18);

skip(hookDetails.honeymoon);
AstariaV1SettlementHook(loan.terms.hook).recall(loan);
vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loan.getId()), abi.encode(true));
vm.expectRevert(abi.encodeWithSelector(BaseRecall.WithdrawDoesNotExist.selector));
AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this)));
}

vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loanId), abi.encode(true));
function testCannotWithdrawLoanHasNotBeenRefinanced() public {
LoanManager.Terms memory terms = LoanManager.Terms({
hook: address(hook),
handler: address(handler),
pricing: address(pricing),
pricingData: defaultPricingData,
handlerData: defaultHandlerData,
hookData: defaultHookData
});
LoanManager.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
vm.expectRevert(abi.encodeWithSelector(BaseRecall.LoanHasNotBeenRefinanced.selector));
AstariaV1SettlementHook(loan.terms.hook).withdraw(loan, payable(address(this)));
}
// //TODO: this needs to be done because withdraw is being looked at
// function testRecallWithdraw() public {
// LoanManager.Terms memory terms = LoanManager.Terms({
// hook: address(hook),
// handler: address(handler),
// pricing: address(pricing),
// pricingData: defaultPricingData,
// handlerData: defaultHandlerData,
// hookData: defaultHookData
// });
// LoanManager.Loan memory loan =
// _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 1e18, terms: terms});
// uint256 loanId = loan.getId();
// BaseRecall.Details memory hookDetails = abi.decode(loan.terms.hookData, (BaseRecall.Details));
//
// erc20s[0].mint(address(this), 10e18);
// erc20s[0].approve(loan.terms.hook, 10e18);
//
// skip(hookDetails.honeymoon);
// AstariaV1SettlementHook(loan.terms.hook).recall(loan);
//
// vm.mockCall(address(LM), abi.encodeWithSelector(LM.inactive.selector, loanId), abi.encode(true));
// }
}

0 comments on commit 832c0a7

Please sign in to comment.