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

Feat: Caveat/Refinance Changes /w active/inactive status renamed to open/closed #83

Merged
merged 6 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
258 changes: 130 additions & 128 deletions .gas-snapshot

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"husky": "^8.0.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.3"
},
"dependencies": {
"mmdc": "^0.0.1"
}
}
6 changes: 3 additions & 3 deletions src/Custodian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ contract Custodian is ERC721, ContractOffererInterface {
function mint(Starport.Loan calldata loan) external {
bytes memory encodedLoan = abi.encode(loan);
uint256 loanId = uint256(keccak256(encodedLoan));
if (loan.custodian != address(this) || !SP.active(loanId)) {
if (loan.custodian != address(this) || SP.closed(loanId)) {
revert InvalidLoan();
}
_safeMint(loan.borrower, loanId, encodedLoan);
Expand All @@ -175,7 +175,7 @@ contract Custodian is ERC721, ContractOffererInterface {
function mintWithApprovalSet(Starport.Loan calldata loan, address approvedTo) external {
bytes memory encodedLoan = abi.encode(loan);
uint256 loanId = uint256(keccak256(encodedLoan));
if (loan.custodian != address(this) || !SP.active(loanId)) {
if (loan.custodian != address(this) || SP.closed(loanId)) {
revert InvalidLoan();
}
if (msg.sender != loan.borrower) {
Expand Down Expand Up @@ -314,7 +314,7 @@ contract Custodian is ERC721, ContractOffererInterface {
) public view returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) {
(Command memory close) = abi.decode(context, (Command));
Starport.Loan memory loan = close.loan;
if (loan.start == block.timestamp || SP.inactive(loan.getId())) {
if (loan.start == block.timestamp || SP.closed(loan.getId())) {
revert InvalidLoan();
}
bool loanActive = Status(loan.terms.status).isActive(loan, close.extraData);
Expand Down
40 changes: 28 additions & 12 deletions src/Starport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {CaveatEnforcer} from "./enforcers/CaveatEnforcer.sol";
import {Custodian} from "./Custodian.sol";
import {PausableNonReentrant} from "./lib/PausableNonReentrant.sol";
import {Pricing} from "./pricing/Pricing.sol";
import {Status} from "./status/Status.sol";
import {Settlement} from "./settlement/Settlement.sol";
import {StarportLib, AdditionalTransfer} from "./lib/StarportLib.sol";

Expand All @@ -56,9 +57,12 @@ contract Starport is PausableNonReentrant {
error AdditionalTransferError();
error CannotTransferLoans();
error CaveatDeadlineExpired();
error InvalidCaveat();
error InvalidCaveatLength();
error InvalidCaveatSigner();
error InvalidCustodian();
error InvalidLoan();
error InvalidLoanState();
error InvalidPostRepayment();
error InvalidRefinance();
error LoanExists();
Expand All @@ -83,8 +87,8 @@ contract Starport is PausableNonReentrant {
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

uint256 public constant LOAN_INACTIVE_FLAG = 0x0;
uint256 public constant LOAN_ACTIVE_FLAG = 0x1;
uint256 public constant LOAN_CLOSED_FLAG = 0x0;
uint256 public constant LOAN_OPEN_FLAG = 0x1;

bytes32 private constant _INVALID_LOAN = 0x045f33d100000000000000000000000000000000000000000000000000000000;
bytes32 private constant _LOAN_EXISTS = 0x14ec57fc00000000000000000000000000000000000000000000000000000000;
Expand Down Expand Up @@ -246,11 +250,15 @@ contract Starport is PausableNonReentrant {
address lender,
CaveatEnforcer.SignedCaveats calldata lenderCaveat,
Starport.Loan memory loan,
bytes calldata pricingData
bytes calldata pricingData,
bytes calldata statusData
) external pausableNonReentrant {
if (loan.start == block.timestamp) {
revert InvalidLoan();
}
if (!Status(loan.terms.status).isActive(loan, statusData)) {
androolloyd marked this conversation as resolved.
Show resolved Hide resolved
revert InvalidLoanState();
}
(
SpentItem[] memory considerationPayment,
SpentItem[] memory carryPayment,
Expand Down Expand Up @@ -426,17 +434,17 @@ contract Starport is PausableNonReentrant {
* @param loanId The id of the loan
* @return bool True if the loan is active
*/
function active(uint256 loanId) public view returns (bool) {
return loanState[loanId] == LOAN_ACTIVE_FLAG;
function open(uint256 loanId) public view returns (bool) {
return loanState[loanId] == LOAN_OPEN_FLAG;
}

/**
* @dev Helper to check if a loan is inactive
* @param loanId The id of the loan
* @return bool True if the loan is inactive
*/
function inactive(uint256 loanId) public view returns (bool) {
return loanState[loanId] == LOAN_INACTIVE_FLAG;
function closed(uint256 loanId) public view returns (bool) {
return loanState[loanId] == LOAN_CLOSED_FLAG;
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down Expand Up @@ -550,10 +558,18 @@ contract Starport is PausableNonReentrant {
revert InvalidCaveatSigner();
}

if (signedCaveats.caveats.length == 0) {
revert InvalidCaveatLength();
}

for (uint256 i = 0; i < signedCaveats.caveats.length;) {
CaveatEnforcer(signedCaveats.caveats[i].enforcer).validate(
additionalTransfers, loan, signedCaveats.caveats[i].data
);
if (
CaveatEnforcer(signedCaveats.caveats[i].enforcer).validate(
additionalTransfers, loan, signedCaveats.caveats[i].data
) != CaveatEnforcer.validate.selector
) {
revert InvalidCaveat();
}
unchecked {
++i;
}
Expand All @@ -580,7 +596,7 @@ contract Starport is PausableNonReentrant {
revert(0x0, 0x04)
}

sstore(loc, LOAN_INACTIVE_FLAG)
sstore(loc, LOAN_CLOSED_FLAG)
}

emit Close(loanId);
Expand Down Expand Up @@ -661,7 +677,7 @@ contract Starport is PausableNonReentrant {
revert(0x0, 0x04)
}

sstore(loc, LOAN_ACTIVE_FLAG)
sstore(loc, LOAN_OPEN_FLAG)
}
emit Open(loanId, loan);
}
Expand Down
3 changes: 2 additions & 1 deletion src/enforcers/BorrowerEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ contract BorrowerEnforcer is CaveatEnforcer {
AdditionalTransfer[] calldata additionalTransfers,
Starport.Loan calldata loan,
bytes calldata caveatData
) public view virtual override {
) public view virtual override returns (bytes4 selector) {
_validate(additionalTransfers, loan, abi.decode(caveatData, (Details)));
selector = CaveatEnforcer.validate.selector;
}

function _validate(
Expand Down
4 changes: 3 additions & 1 deletion src/enforcers/BorrowerEnforcerBNPL.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract BorrowerEnforcerBNPL is CaveatEnforcer {
AdditionalTransfer[] calldata additionalTransfers,
Starport.Loan calldata loan,
bytes calldata caveatData
) public view virtual override {
) public view virtual override returns (bytes4 selector) {
bytes32 loanHash = keccak256(abi.encode(loan));

Details memory details = abi.decode(caveatData, (Details));
Expand Down Expand Up @@ -106,5 +106,7 @@ contract BorrowerEnforcerBNPL is CaveatEnforcer {
revert InvalidAdditionalTransfer();
}
}

selector = CaveatEnforcer.validate.selector;
}
}
3 changes: 2 additions & 1 deletion src/enforcers/CaveatEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ abstract contract CaveatEnforcer {
function validate(AdditionalTransfer[] calldata solution, Starport.Loan calldata loan, bytes calldata caveatData)
public
view
virtual;
virtual
returns (bytes4);
}
3 changes: 2 additions & 1 deletion src/enforcers/LenderEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ contract LenderEnforcer is CaveatEnforcer {
AdditionalTransfer[] calldata additionalTransfers,
Starport.Loan calldata loan,
bytes calldata caveatData
) public view virtual override {
) public view virtual override returns (bytes4 selector) {
_validate(additionalTransfers, loan, abi.decode(caveatData, (Details)));
selector = CaveatEnforcer.validate.selector;
}

function _validate(
Expand Down
2 changes: 1 addition & 1 deletion test/StarportTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ contract StarportTest is BaseOrderTest, Stargate {
if (revertMessage.length > 0) {
vm.expectRevert(revertMessage); //reverts InvalidContractOfferer with an address an a contract nonce so expect general revert
}
SP.refinance(lender, lenderCaveat, loan, pricingData);
SP.refinance(lender, lenderCaveat, loan, pricingData, "");

vm.stopPrank();

Expand Down
18 changes: 11 additions & 7 deletions test/fuzz-testing/TestFuzzStarport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,10 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
badLoan.originator = goodLoan.originator;

assert(goodLoan.originator != address(0));
assert(SP.active(goodLoan.getId()));
assert(!SP.inactive(goodLoan.getId()));
assert(SP.inactive(badLoan.getId()));
assert(!SP.active(badLoan.getId()));
assert(SP.open(goodLoan.getId()));
assert(!SP.closed(goodLoan.getId()));
assert(SP.closed(badLoan.getId()));
assert(!SP.open(badLoan.getId()));
}

function testFuzzRepaymentFails(FuzzRepaymentLoan memory params) public {
Expand Down Expand Up @@ -599,8 +599,11 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
Account memory account = makeAndAllocateAccount(params.refiKey);

address refiFulfiller;
skip(1);
skip(_boundMax(params.skipTime, abi.decode(goodLoan.terms.statusData, (FixedTermStatus.Details)).loanDuration));
skip(
_bound(
params.skipTime, 1, abi.decode(goodLoan.terms.statusData, (FixedTermStatus.Details)).loanDuration - 1
)
);
(
SpentItem[] memory considerationPayment,
SpentItem[] memory carryPayment,
Expand Down Expand Up @@ -641,7 +644,8 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
account.addr,
refiFulfiller != account.addr ? lenderCaveat : _emptyCaveat(),
goodLoan2,
abi.encode(newPricingDetails)
abi.encode(newPricingDetails),
""
);
}
}
Expand Down
62 changes: 56 additions & 6 deletions test/integration-testing/TestCaveats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,56 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {
SP.originate(new AdditionalTransfer[](0), borrowerCaveat, _emptyCaveat(), loan);
}

function testInvalidCaveats() public {
Starport.Loan memory loan = generateDefaultLoanTerms();

CaveatEnforcer.SignedCaveats memory borrowerCaveat = getBorrowerSignedCaveat({
details: BorrowerEnforcer.Details({loan: loan}),
signer: borrower,
salt: bytes32(0),
enforcer: address(borrowerEnforcer)
});
_setApprovalsForSpentItems(borrower.addr, loan.collateral);

_setApprovalsForSpentItems(lender.addr, loan.debt);

vm.roll(5);
vm.mockCall(
address(borrowerEnforcer),
abi.encodeWithSelector(
CaveatEnforcer.validate.selector, new AdditionalTransfer[](0), loan, borrowerCaveat.caveats[0].data
),
abi.encode(bytes4(0))
);

vm.expectRevert(Starport.InvalidCaveat.selector);
vm.prank(lender.addr);
SP.originate(new AdditionalTransfer[](0), borrowerCaveat, _emptyCaveat(), loan);
}

function testInvalidCaveatLength() public {
Starport.Loan memory loan = generateDefaultLoanTerms();

CaveatEnforcer.SignedCaveats memory signedCaveats;
signedCaveats.caveats = new CaveatEnforcer.Caveat[](0);
signedCaveats.salt = bytes32(0);
signedCaveats.singleUse = true;
signedCaveats.deadline = block.timestamp + 1 days;
bytes32 hash = SP.hashCaveatWithSaltAndNonce(
borrower.addr, signedCaveats.singleUse, signedCaveats.salt, signedCaveats.deadline, signedCaveats.caveats
);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(borrower.key, hash);
signedCaveats.signature = abi.encodePacked(r, s, v);
_setApprovalsForSpentItems(borrower.addr, loan.collateral);

_setApprovalsForSpentItems(lender.addr, loan.debt);

vm.expectRevert(Starport.InvalidCaveatLength.selector);
vm.prank(lender.addr);
SP.originate(new AdditionalTransfer[](0), signedCaveats, _emptyCaveat(), loan);
}

function testOriginateWBorrowerApproval() public {
Starport.Loan memory loan = generateDefaultLoanTerms();

Expand Down Expand Up @@ -205,7 +255,7 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {
vm.warp(block.timestamp + 1);
mockIsValidRefinanceCall(loan.terms.pricing, loan.debt, new SpentItem[](0), new AdditionalTransfer[](0));
vm.expectRevert(StarportLib.InvalidSalt.selector);
SP.refinance(lender.addr, lenderCaveat, loan, "");
SP.refinance(lender.addr, lenderCaveat, loan, "", "");
}

function testRefinanceAsLender() public {
Expand All @@ -218,7 +268,7 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {
vm.warp(block.timestamp + 1);
vm.prank(newLender);
mockIsValidRefinanceCall(loan.terms.pricing, loan.debt, new SpentItem[](0), new AdditionalTransfer[](0));
SP.refinance(newLender, _emptyCaveat(), loan, defaultPricingData);
SP.refinance(newLender, _emptyCaveat(), loan, defaultPricingData, "");
}

function testRefinanceWLenderApproval() public {
Expand All @@ -232,7 +282,7 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {
vm.warp(block.timestamp + 1);
vm.prank(borrower.addr);
mockIsValidRefinanceCall(loan.terms.pricing, loan.debt, new SpentItem[](0), new AdditionalTransfer[](0));
SP.refinance(lender.addr, _emptyCaveat(), loan, defaultPricingData);
SP.refinance(lender.addr, _emptyCaveat(), loan, defaultPricingData, "");
}

function testRefinanceUnapprovedFulfiller() public {
Expand Down Expand Up @@ -261,7 +311,7 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {

mockIsValidRefinanceCall(loan.terms.pricing, loan.debt, new SpentItem[](0), new AdditionalTransfer[](0));

SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData);
SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData, "");
}

function testRefinanceCaveatFailure() public {
Expand All @@ -280,7 +330,7 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {
vm.prank(loan.borrower);
mockIsValidRefinanceCall(loan.terms.pricing, loan.debt, new SpentItem[](0), new AdditionalTransfer[](0));
vm.expectRevert(LenderEnforcer.InvalidLoanTerms.selector);
SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData);
SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData, "");
}

function testRefinanceLoanStartAtBlockTimestampInvalidLoan() public {
Expand All @@ -297,6 +347,6 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {

vm.prank(loan.borrower);
vm.expectRevert(Starport.InvalidLoan.selector);
SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData);
SP.refinance(lender.addr, lenderCaveat, loan, defaultPricingData, "");
}
}
2 changes: 1 addition & 1 deletion test/integration-testing/TestLoanCombinations.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract TestLoanCombinations is StarportTest {
assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20");

uint256 loanId = loan.getId();
assertTrue(SP.active(loanId), "LoanId not in active state after a new loan");
assertTrue(SP.open(loanId), "LoanId not in active state after a new loan");
skip(10 days);

_repayLoan({borrower: borrower.addr, amount: 375, loan: loan});
Expand Down
Loading