diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dde6e2eb..b4ead371 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,6 @@ env: jobs: test: runs-on: ubuntu-latest - steps: - name: Checkout repository uses: actions/checkout@v3 @@ -19,7 +18,18 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 + - run: yarn + - uses: onbjerg/foundry-toolchain@v1 - - run: forge test --ffi -vvv \ No newline at end of file + + - name: lint + run: forge fmt --check + + - name: test + run: forge test --ffi -vvv + + - name: snapshot + run: forge snapshot + diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..77a7d10b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +forge fmt && git update-index --again diff --git a/.husky/pre-commit.sh b/.husky/pre-commit.sh new file mode 100644 index 00000000..29e367b1 --- /dev/null +++ b/.husky/pre-commit.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +forge fmt diff --git a/package.json b/package.json index 579a2903..e008f340 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "dependencies": { "@chainlink/contracts": "^0.6.1", "chai": "^4.3.7", + "husky": "^8.0.3", "mocha": "^10.2.0", "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.1.3" diff --git a/src/CapitalPool.sol b/src/CapitalPool.sol index e6ff07f6..bc17b325 100644 --- a/src/CapitalPool.sol +++ b/src/CapitalPool.sol @@ -2,52 +2,45 @@ pragma solidity ^0.8.17; import {ERC20, ERC4626} from "solady/src/tokens/ERC4626.sol"; import {LoanManager} from "src/LoanManager.sol"; -import { - ConduitControllerInterface -} from "seaport-sol/src/ConduitControllerInterface.sol"; +import {ConduitControllerInterface} from "seaport-sol/src/ConduitControllerInterface.sol"; import "forge-std/console.sol"; contract CapitalPool is ERC4626 { - address immutable underlying; - - bytes32 conduitKey; - address public immutable conduit; - - constructor( - address underlying_, - ConduitControllerInterface cc_, - address originator_ - ) { - bytes32 ck = bytes32(uint256(uint160(address(address(this)))) << 96); - address c = cc_.createConduit(ck, address(this)); - cc_.updateChannel(c, originator_, true); - ERC20(underlying_).approve(c, type(uint256).max); - conduit = c; - conduitKey = ck; - underlying = underlying_; - } - - function asset() public view override returns (address) { - return underlying; - } - - function name() public pure override returns (string memory) { - return "AstariaV1Pool"; - } - - function symbol() public pure override returns (string memory) { - return "AV1P"; - } - - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) public pure returns (bytes4) { - LoanManager.Loan memory loan = abi.decode(data, (LoanManager.Loan)); - //handle any logic here from when you receive a loan - return this.onERC721Received.selector; - } + address immutable underlying; + + bytes32 conduitKey; + address public immutable conduit; + + constructor(address underlying_, ConduitControllerInterface cc_, address originator_) { + bytes32 ck = bytes32(uint256(uint160(address(address(this)))) << 96); + address c = cc_.createConduit(ck, address(this)); + cc_.updateChannel(c, originator_, true); + ERC20(underlying_).approve(c, type(uint256).max); + conduit = c; + conduitKey = ck; + underlying = underlying_; + } + + function asset() public view override returns (address) { + return underlying; + } + + function name() public pure override returns (string memory) { + return "AstariaV1Pool"; + } + + function symbol() public pure override returns (string memory) { + return "AV1P"; + } + + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + public + pure + returns (bytes4) + { + LoanManager.Loan memory loan = abi.decode(data, (LoanManager.Loan)); + //handle any logic here from when you receive a loan + return this.onERC721Received.selector; + } } diff --git a/src/ConduitHelper.sol b/src/ConduitHelper.sol index b72fcdb8..6380b662 100644 --- a/src/ConduitHelper.sol +++ b/src/ConduitHelper.sol @@ -4,20 +4,10 @@ import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; import {ERC1155} from "solady/src/tokens/ERC1155.sol"; -import { - ItemType, - OfferItem, - Schema, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import { - ContractOffererInterface -} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; -import { - ConsiderationInterface -} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; +import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {Originator} from "src/originators/Originator.sol"; import {SettlementHook} from "src/hooks/SettlementHook.sol"; @@ -27,177 +17,164 @@ import {Pricing} from "src/pricing/Pricing.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; import "forge-std/console2.sol"; -import { - ConduitTransfer, - ConduitItemType -} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; -import { - ConduitControllerInterface -} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; -import { - ConduitInterface -} from "seaport-types/src/interfaces/ConduitInterface.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; +import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {Custodian} from "src/Custodian.sol"; import {ECDSA} from "solady/src/utils/ECDSA.sol"; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; import {CaveatEnforcer} from "src/enforcers/CaveatEnforcer.sol"; abstract contract ConduitHelper { - error RepayCarryLengthMismatch(); - - // TODO: Greg pls help us unfuck this mess - function _mergeConsiderations( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory additionalConsiderations - ) internal returns (ReceivedItem[] memory consideration) { - if ( - carryConsideration.length == 0 && additionalConsiderations.length == 0 - ) { - return repayConsideration; - } - consideration = new ReceivedItem[]( + error RepayCarryLengthMismatch(); + + // TODO: Greg pls help us unfuck this mess + function _mergeConsiderations( + ReceivedItem[] memory repayConsideration, + ReceivedItem[] memory carryConsideration, + ReceivedItem[] memory additionalConsiderations + ) internal returns (ReceivedItem[] memory consideration) { + if (carryConsideration.length == 0 && additionalConsiderations.length == 0) { + return repayConsideration; + } + consideration = new ReceivedItem[]( repayConsideration.length + carryConsideration.length + additionalConsiderations.length ); - uint256 j = 0; - // if there is a carry to handle, subtract it from the amount owed - if (carryConsideration.length > 0) { - if (repayConsideration.length != carryConsideration.length) - revert RepayCarryLengthMismatch(); - uint256 i = 0; - for (; i < repayConsideration.length; ) { - repayConsideration[i].amount -= carryConsideration[i].amount; - consideration[j] = repayConsideration[i]; - unchecked { - ++i; - ++j; - } - } - i = 0; - for (; i < carryConsideration.length; ) { - consideration[j] = carryConsideration[i]; - unchecked { - ++i; - ++j; + uint256 j = 0; + // if there is a carry to handle, subtract it from the amount owed + if (carryConsideration.length > 0) { + if (repayConsideration.length != carryConsideration.length) { + revert RepayCarryLengthMismatch(); + } + uint256 i = 0; + for (; i < repayConsideration.length;) { + repayConsideration[i].amount -= carryConsideration[i].amount; + consideration[j] = repayConsideration[i]; + unchecked { + ++i; + ++j; + } + } + i = 0; + for (; i < carryConsideration.length;) { + consideration[j] = carryConsideration[i]; + unchecked { + ++i; + ++j; + } + } } - } - } - // else just use the consideration payment only - else { - uint256 i = 0; - for (; i < repayConsideration.length; ) { - consideration[j] = repayConsideration[i]; - unchecked { - ++i; - ++j; - } - } - } - - if (additionalConsiderations.length > 0) { - uint256 i = 0; - for (; i < additionalConsiderations.length; ) { - if ( - consideration[i].recipient == additionalConsiderations[i].recipient && - consideration[i].token == additionalConsiderations[i].token - ) { - consideration[i].amount += additionalConsiderations[i].amount; - unchecked { - ++i; - } - } else { - consideration[j] = additionalConsiderations[i]; - unchecked { - ++i; - ++j; - } + // else just use the consideration payment only + else { + uint256 i = 0; + for (; i < repayConsideration.length;) { + consideration[j] = repayConsideration[i]; + unchecked { + ++i; + ++j; + } + } } - } - } - } - function _removeZeroAmounts( - ReceivedItem[] memory consideration - ) internal view returns (ReceivedItem[] memory newConsideration) { - uint256 i = 0; - uint256 validConsiderations = 0; - for (; i < consideration.length; ) { - if (consideration[i].amount > 0) ++validConsiderations; - unchecked { - ++i; - } - } - i = 0; - uint256 j = 0; - newConsideration = new ReceivedItem[](validConsiderations); - for (; i < consideration.length; ) { - if (consideration[i].amount > 0) { - newConsideration[j] = consideration[i]; - unchecked { - ++j; + if (additionalConsiderations.length > 0) { + uint256 i = 0; + for (; i < additionalConsiderations.length;) { + if ( + consideration[i].recipient == additionalConsiderations[i].recipient + && consideration[i].token == additionalConsiderations[i].token + ) { + consideration[i].amount += additionalConsiderations[i].amount; + unchecked { + ++i; + } + } else { + consideration[j] = additionalConsiderations[i]; + unchecked { + ++i; + ++j; + } + } + } } - } - unchecked { - ++i; - } - } - } - - function _packageTransfers( - ReceivedItem[] memory refinanceConsideration, - address refinancer - ) internal pure returns (ConduitTransfer[] memory transfers) { - uint256 i = 0; - uint256 validConsiderations = 0; - for (; i < refinanceConsideration.length; ) { - if (refinanceConsideration[i].amount > 0) ++validConsiderations; - unchecked { - ++i; - } } - transfers = new ConduitTransfer[](validConsiderations); - i = 0; - uint256 j = 0; - for (; i < refinanceConsideration.length; ) { - ConduitItemType itemType; - ReceivedItem memory debt = refinanceConsideration[i]; - - assembly { - itemType := mload(debt) - switch itemType - case 1 { + function _removeZeroAmounts(ReceivedItem[] memory consideration) + internal + view + returns (ReceivedItem[] memory newConsideration) + { + uint256 i = 0; + uint256 validConsiderations = 0; + for (; i < consideration.length;) { + if (consideration[i].amount > 0) ++validConsiderations; + unchecked { + ++i; + } } - case 2 { - + i = 0; + uint256 j = 0; + newConsideration = new ReceivedItem[](validConsiderations); + for (; i < consideration.length;) { + if (consideration[i].amount > 0) { + newConsideration[j] = consideration[i]; + unchecked { + ++j; + } + } + unchecked { + ++i; + } } - case 3 { + } + function _packageTransfers(ReceivedItem[] memory refinanceConsideration, address refinancer) + internal + pure + returns (ConduitTransfer[] memory transfers) + { + uint256 i = 0; + uint256 validConsiderations = 0; + for (; i < refinanceConsideration.length;) { + if (refinanceConsideration[i].amount > 0) ++validConsiderations; + unchecked { + ++i; + } } - default { - revert(0, 0) - } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) - } - if (refinanceConsideration[i].amount > 0) { - transfers[j] = ConduitTransfer({ - itemType: itemType, - from: refinancer, - token: refinanceConsideration[i].token, - identifier: refinanceConsideration[i].identifier, - amount: refinanceConsideration[i].amount, - to: refinanceConsideration[i].recipient - }); - unchecked { - ++j; + transfers = new ConduitTransfer[](validConsiderations); + i = 0; + uint256 j = 0; + for (; i < refinanceConsideration.length;) { + ConduitItemType itemType; + ReceivedItem memory debt = refinanceConsideration[i]; + + assembly { + itemType := mload(debt) + switch itemType + case 1 {} + case 2 {} + case 3 {} + default { revert(0, 0) } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) + } + if (refinanceConsideration[i].amount > 0) { + transfers[j] = ConduitTransfer({ + itemType: itemType, + from: refinancer, + token: refinanceConsideration[i].token, + identifier: refinanceConsideration[i].identifier, + amount: refinanceConsideration[i].amount, + to: refinanceConsideration[i].recipient + }); + unchecked { + ++j; + } + } + + unchecked { + ++i; + } } - } - - unchecked { - ++i; - } } - } } diff --git a/src/Custodian.sol b/src/Custodian.sol index 96946be3..4163cad0 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -4,19 +4,10 @@ import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; import {ERC1155} from "solady/src/tokens/ERC1155.sol"; -import { - ItemType, - Schema, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; - -import { - ContractOffererInterface -} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; -import { - TokenReceiverInterface -} from "src/interfaces/TokenReceiverInterface.sol"; +import {ItemType, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; + +import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; +import {TokenReceiverInterface} from "src/interfaces/TokenReceiverInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {Originator} from "src/originators/Originator.sol"; import {SettlementHook} from "src/hooks/SettlementHook.sol"; @@ -27,270 +18,231 @@ import "forge-std/console.sol"; import {ConduitHelper} from "src/ConduitHelper.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; -contract Custodian is - ContractOffererInterface, - TokenReceiverInterface, - ConduitHelper -{ - using {StarPortLib.getId} for LoanManager.Loan; - LoanManager public immutable LM; - address public immutable seaport; - event SeaportCompatibleContractDeployed(); - - constructor(LoanManager LM_, address seaport_) { - seaport = seaport_; - LM = LM_; - emit SeaportCompatibleContractDeployed(); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ContractOffererInterface) returns (bool) { - return interfaceId == type(ContractOffererInterface).interfaceId; - } - - modifier onlySeaport() { - if (msg.sender != address(seaport)) { - revert InvalidSender(); +contract Custodian is ContractOffererInterface, TokenReceiverInterface, ConduitHelper { + using {StarPortLib.getId} for LoanManager.Loan; + + LoanManager public immutable LM; + address public immutable seaport; + + event SeaportCompatibleContractDeployed(); + + constructor(LoanManager LM_, address seaport_) { + seaport = seaport_; + LM = LM_; + emit SeaportCompatibleContractDeployed(); } - _; - } - - error InvalidSender(); - error InvalidHandler(); - - /** - * @dev Generates the order for this contract offerer. - * - * @param offer The address of the contract fulfiller. - * @param consideration The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @param orderHashes The context of the order. - * @param contractNonce The context of the order. - * @return ratifyOrderMagicValue The magic value returned by the ratify. - */ - function ratifyOrder( - SpentItem[] calldata offer, - ReceivedItem[] calldata consideration, - bytes calldata context, // encoded based on the schemaID - bytes32[] calldata orderHashes, - uint256 contractNonce - ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { - LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); - //ensure loan is valid against what we have to deliver to seaport - - // we burn the loan on repayment in generateOrder, but in ratify order where we would trigger any post settlement actions - // we burn it here so that in the case it was minted and an owner is set for settlement their pointer can still be utilized - // in this case we are not a repayment we have burnt the loan in the generate order for a repayment - uint256 loanId = loan.getId(); - if (LM.active(loanId)) { - if ( - SettlementHandler(loan.terms.handler).execute(loan) != - SettlementHandler.execute.selector - ) { - revert InvalidHandler(); - } - - if (loan.issuer.code.length > 0) { - //callback on the issuer - //if supportsInterface() then do this - } - _settleLoan(loan); + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ContractOffererInterface) + returns (bool) + { + return interfaceId == type(ContractOffererInterface).interfaceId; } - ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; - } - - function custody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) external virtual returns (bytes4 selector) { - selector = Custodian.custody.selector; - } - - /** - * @dev Generates the order for this contract offerer. - * - * @param fulfiller The address of the contract fulfiller. - * @param maximumSpent The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function generateOrder( - address fulfiller, - SpentItem[] calldata minimumReceived, - SpentItem[] calldata maximumSpent, - bytes calldata context // encoded based on the schemaID - ) - external - onlySeaport - returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) - { - LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); - offer = loan.collateral; - - if (SettlementHook(loan.terms.hook).isActive(loan)) { - if (fulfiller != _getBorrower(loan)) { - revert InvalidSender(); - } - - ( - ReceivedItem[] memory paymentConsiderations, - ReceivedItem[] memory carryFeeConsideration - ) = Pricing(loan.terms.pricing).getPaymentConsideration(loan); - - consideration = _mergeConsiderations( - paymentConsiderations, - carryFeeConsideration, - new ReceivedItem[](0) - ); - consideration = _removeZeroAmounts(consideration); - //if a callback is needed for the issuer do it here - _settleLoan(loan); - } else { - address restricted; - //add in originator fee - _beforeSettlementHandlerHook(loan); - (consideration, restricted) = SettlementHandler(loan.terms.handler) - .getSettlement(loan); - _afterSettlementHandlerHook(loan); - - if (restricted != address(0) && fulfiller != restricted) { - revert InvalidSender(); - } + + modifier onlySeaport() { + if (msg.sender != address(seaport)) { + revert InvalidSender(); + } + _; } - if (offer.length > 0) { - _beforeApprovalsSetHook(fulfiller, maximumSpent, context); - _setOfferApprovals(offer, seaport); + error InvalidSender(); + error InvalidHandler(); + + /** + * @dev Generates the order for this contract offerer. + * + * @param offer The address of the contract fulfiller. + * @param consideration The maximum amount of items to be spent by the order. + * @param context The context of the order. + * @param orderHashes The context of the order. + * @param contractNonce The context of the order. + * @return ratifyOrderMagicValue The magic value returned by the ratify. + */ + function ratifyOrder( + SpentItem[] calldata offer, + ReceivedItem[] calldata consideration, + bytes calldata context, // encoded based on the schemaID + bytes32[] calldata orderHashes, + uint256 contractNonce + ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { + LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); + //ensure loan is valid against what we have to deliver to seaport + + // we burn the loan on repayment in generateOrder, but in ratify order where we would trigger any post settlement actions + // we burn it here so that in the case it was minted and an owner is set for settlement their pointer can still be utilized + // in this case we are not a repayment we have burnt the loan in the generate order for a repayment + uint256 loanId = loan.getId(); + if (LM.active(loanId)) { + if (SettlementHandler(loan.terms.handler).execute(loan) != SettlementHandler.execute.selector) { + revert InvalidHandler(); + } + + if (loan.issuer.code.length > 0) { + //callback on the issuer + //if supportsInterface() then do this + } + _settleLoan(loan); + } + ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; + } + + function custody( + ReceivedItem[] calldata consideration, + bytes32[] calldata orderHashes, + uint256 contractNonce, + bytes calldata context + ) external virtual returns (bytes4 selector) { + selector = Custodian.custody.selector; + } + + /** + * @dev Generates the order for this contract offerer. + * + * @param fulfiller The address of the contract fulfiller. + * @param maximumSpent The maximum amount of items to be spent by the order. + * @param context The context of the order. + * @return offer The items spent by the order. + * @return consideration The items received by the order. + */ + function generateOrder( + address fulfiller, + SpentItem[] calldata minimumReceived, + SpentItem[] calldata maximumSpent, + bytes calldata context // encoded based on the schemaID + ) external onlySeaport returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { + LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); + offer = loan.collateral; + + if (SettlementHook(loan.terms.hook).isActive(loan)) { + if (fulfiller != _getBorrower(loan)) { + revert InvalidSender(); + } + + (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = + Pricing(loan.terms.pricing).getPaymentConsideration(loan); + + consideration = _mergeConsiderations(paymentConsiderations, carryFeeConsideration, new ReceivedItem[](0)); + consideration = _removeZeroAmounts(consideration); + //if a callback is needed for the issuer do it here + _settleLoan(loan); + } else { + address restricted; + //add in originator fee + _beforeSettlementHandlerHook(loan); + (consideration, restricted) = SettlementHandler(loan.terms.handler).getSettlement(loan); + _afterSettlementHandlerHook(loan); + + if (restricted != address(0) && fulfiller != restricted) { + revert InvalidSender(); + } + } + + if (offer.length > 0) { + _beforeApprovalsSetHook(fulfiller, maximumSpent, context); + _setOfferApprovals(offer, seaport); + } } - } - - function _beforeApprovalsSetHook( - address fulfiller, - SpentItem[] calldata maximumSpent, - bytes calldata context - ) internal virtual {} - - function _beforeSettlementHandlerHook( - LoanManager.Loan memory loan - ) internal virtual {} - - function _afterSettlementHandlerHook( - LoanManager.Loan memory loan - ) internal virtual {} - - function _beforeSettleLoanHook( - LoanManager.Loan memory loan - ) internal virtual {} - - function _afterSettleLoanHook( - LoanManager.Loan memory loan - ) internal virtual {} - - function _setOfferApprovals( - SpentItem[] memory offer, - address target - ) internal { - for (uint256 i = 0; i < offer.length; i++) { - //approve consideration based on item type - if (offer[i].itemType == ItemType.ERC1155) { - ERC1155(offer[i].token).setApprovalForAll(target, true); - } else if (offer[i].itemType == ItemType.ERC721) { - ERC721(offer[i].token).setApprovalForAll(target, true); - } else if (offer[i].itemType == ItemType.ERC20) { - uint256 allowance = ERC20(offer[i].token).allowance( - address(this), - target - ); - if (allowance != 0) { - ERC20(offer[i].token).approve(target, 0); + + function _beforeApprovalsSetHook(address fulfiller, SpentItem[] calldata maximumSpent, bytes calldata context) + internal + virtual + {} + + function _beforeSettlementHandlerHook(LoanManager.Loan memory loan) internal virtual {} + + function _afterSettlementHandlerHook(LoanManager.Loan memory loan) internal virtual {} + + function _beforeSettleLoanHook(LoanManager.Loan memory loan) internal virtual {} + + function _afterSettleLoanHook(LoanManager.Loan memory loan) internal virtual {} + + function _setOfferApprovals(SpentItem[] memory offer, address target) internal { + for (uint256 i = 0; i < offer.length; i++) { + //approve consideration based on item type + if (offer[i].itemType == ItemType.ERC1155) { + ERC1155(offer[i].token).setApprovalForAll(target, true); + } else if (offer[i].itemType == ItemType.ERC721) { + ERC721(offer[i].token).setApprovalForAll(target, true); + } else if (offer[i].itemType == ItemType.ERC20) { + uint256 allowance = ERC20(offer[i].token).allowance(address(this), target); + if (allowance != 0) { + ERC20(offer[i].token).approve(target, 0); + } + ERC20(offer[i].token).approve(target, offer[i].amount); + } } - ERC20(offer[i].token).approve(target, offer[i].amount); - } } - } - - /** - * @dev previews the order for this contract offerer. - * - * @param caller The address of the contract fulfiller. - * @param fulfiller The address of the contract fulfiller. - * @param minimumReceived The minimum the fulfiller must receive. - * @param maximumSpent The most a fulfiller will spend - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function previewOrder( - address caller, - address fulfiller, - SpentItem[] calldata minimumReceived, - SpentItem[] calldata maximumSpent, - bytes calldata context // encoded based on the schemaID - ) - public - view - returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) - { - //TODO: move this into generate order and then do a view only version that doesnt call settle - } - - //todo work with seaport - function getSeaportMetadata() - external - pure - returns (string memory, Schema[] memory schemas) - { - //adhere to sip data, how to encode the context and what it is - //TODO: add in the context for the loan - //you need to parse LM Open events for the loan and abi encode it - schemas = new Schema[](1); - schemas[0] = Schema(8, ""); - return ("Loans", schemas); - } - - // PUBLIC FUNCTIONS - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) public pure virtual returns (bytes4) { - return TokenReceiverInterface.onERC721Received.selector; - } - - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes calldata - ) external pure virtual returns (bytes4) { - return TokenReceiverInterface.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] calldata, - uint256[] calldata, - bytes calldata - ) external pure virtual returns (bytes4) { - return TokenReceiverInterface.onERC1155BatchReceived.selector; - } - - function _getBorrower( - LoanManager.Loan memory loan - ) internal view virtual returns (address) { - return loan.borrower; - } - - function _settleLoan(LoanManager.Loan memory loan) internal virtual { - _beforeSettleLoanHook(loan); - LM.settle(loan); - _afterSettleLoanHook(loan); - } + + /** + * @dev previews the order for this contract offerer. + * + * @param caller The address of the contract fulfiller. + * @param fulfiller The address of the contract fulfiller. + * @param minimumReceived The minimum the fulfiller must receive. + * @param maximumSpent The most a fulfiller will spend + * @param context The context of the order. + * @return offer The items spent by the order. + * @return consideration The items received by the order. + */ + function previewOrder( + address caller, + address fulfiller, + SpentItem[] calldata minimumReceived, + SpentItem[] calldata maximumSpent, + bytes calldata context // encoded based on the schemaID + ) public view returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { + //TODO: move this into generate order and then do a view only version that doesnt call settle + } + + //todo work with seaport + function getSeaportMetadata() external pure returns (string memory, Schema[] memory schemas) { + //adhere to sip data, how to encode the context and what it is + //TODO: add in the context for the loan + //you need to parse LM Open events for the loan and abi encode it + schemas = new Schema[](1); + schemas[0] = Schema(8, ""); + return ("Loans", schemas); + } + + // PUBLIC FUNCTIONS + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + public + pure + virtual + returns (bytes4) + { + return TokenReceiverInterface.onERC721Received.selector; + } + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) + external + pure + virtual + returns (bytes4) + { + return TokenReceiverInterface.onERC1155Received.selector; + } + + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) + external + pure + virtual + returns (bytes4) + { + return TokenReceiverInterface.onERC1155BatchReceived.selector; + } + + function _getBorrower(LoanManager.Loan memory loan) internal view virtual returns (address) { + return loan.borrower; + } + + function _settleLoan(LoanManager.Loan memory loan) internal virtual { + _beforeSettleLoanHook(loan); + LM.settle(loan); + _afterSettleLoanHook(loan); + } } diff --git a/src/LoanManager.sol b/src/LoanManager.sol index 44e5fcef..ffc53094 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -4,20 +4,10 @@ import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; import {ERC1155} from "solady/src/tokens/ERC1155.sol"; -import { - ItemType, - OfferItem, - Schema, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; - -import { - ContractOffererInterface -} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; -import { - ConsiderationInterface -} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; +import {ItemType, OfferItem, Schema, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; + +import {ContractOffererInterface} from "seaport-types/src/interfaces/ContractOffererInterface.sol"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {Originator} from "src/originators/Originator.sol"; import {SettlementHook} from "src/hooks/SettlementHook.sol"; @@ -27,16 +17,9 @@ import {Pricing} from "src/pricing/Pricing.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; import "forge-std/console2.sol"; -import { - ConduitTransfer, - ConduitItemType -} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; -import { - ConduitControllerInterface -} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; -import { - ConduitInterface -} from "seaport-types/src/interfaces/ConduitInterface.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; +import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {Custodian} from "src/Custodian.sol"; import {ECDSA} from "solady/src/utils/ECDSA.sol"; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; @@ -45,563 +28,481 @@ import {CaveatEnforcer} from "src/enforcers/CaveatEnforcer.sol"; import {ConduitHelper} from "src/ConduitHelper.sol"; contract LoanManager is ERC721, ContractOffererInterface, ConduitHelper { - using FixedPointMathLib for uint256; - using {StarPortLib.toReceivedItems} for SpentItem[]; - using {StarPortLib.getId} for LoanManager.Loan; - - ConsiderationInterface public constant seaport = - ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); - // ConsiderationInterface public constant seaport = - // ConsiderationInterface(0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC); // mainnet - address public immutable defaultCustodian; - bytes32 public immutable DEFAULT_CUSTODIAN_CODE_HASH; - // uint256 public fee; - // uint256 private constant ONE_WORD = 0x20; - - // Define the EIP712 domain and typehash constants for generating signatures - bytes32 constant EIP_DOMAIN = - keccak256( - "EIP712Domain(string version,uint256 chainId,address verifyingContract)" - ); - bytes32 public constant INTENT_ORIGINATION_TYPEHASH = - keccak256("IntentOrigination(bytes32 hash,bytes32 salt,uint256 nonce)"); - bytes32 constant VERSION = keccak256("0"); - - bytes32 internal immutable _DOMAIN_SEPARATOR; - - //TODO: we need to add in type hashes into the hashes - mapping(bytes32 => bool) public usedHashes; - mapping(address => uint256) public borrowerNonce; //needs to be invalidated - - enum FieldFlags { - INITIALIZED, - ACTIVE, - INACTIVE - } - - struct Terms { - address hook; //the address of the hookmodule - bytes hookData; //bytes encoded hook data - address pricing; //the address o the pricing module - bytes pricingData; //bytes encoded pricing data - address handler; //the address of the handler module - bytes handlerData; //bytes encoded handler data - } - - struct Loan { - uint256 start; //start of the loan - address custodian; //where the collateral is being held - address borrower; //the borrower - address issuer; //the capital issuer/lender - address originator; //who originated the loan - SpentItem[] collateral; //array of collateral - SpentItem[] debt; //array of debt - Terms terms; //the actionable terms of the loan - } - - struct Caveat { - address enforcer; - bytes terms; - } - - struct Obligation { - address custodian; - address originator; - address borrower; - bytes32 salt; - SpentItem[] debt; - Caveat[] caveats; - bytes details; - bytes signature; - } - - event Close(uint256 loanId); - event Open(uint256 loanId, LoanManager.Loan loan); - event SeaportCompatibleContractDeployed(); - - error ConduitTransferError(); - error InvalidConduit(); - error InvalidRefinance(); - error InvalidSender(); - error InvalidAction(); - error InvalidLoan(uint256); - error InvalidMaximumSpentEmpty(); - error InvalidDebtEmpty(); - error InvalidAmount(); - error InvalidDuration(); - error InvalidSignature(); - error InvalidOrigination(); - error InvalidSigner(); - error InvalidContext(ContextErrors); - error InvalidNoRefinanceConsideration(); - - enum ContextErrors { - BAD_ORIGINATION, - INVALID_PAYMENT, - LENGTH_MISMATCH, - BORROWER_MISMATCH, - COLLATERAL, - ZERO_ADDRESS, - INVALID_LOAN, - INVALID_CONDUIT, - INVALID_RESOLVER, - INVALID_COLLATERAL - } - - constructor() { - address custodian = address(new Custodian(this, address(seaport))); - - bytes32 defaultCustodianCodeHash; - assembly { - defaultCustodianCodeHash := extcodehash(custodian) - } - defaultCustodian = custodian; - DEFAULT_CUSTODIAN_CODE_HASH = defaultCustodianCodeHash; - _DOMAIN_SEPARATOR = keccak256( - abi.encode(EIP_DOMAIN, VERSION, block.chainid, address(this)) - ); - emit SeaportCompatibleContractDeployed(); - } - - // Encode the data with the account's nonce for generating a signature - function encodeWithSaltAndBorrowerCounter( - address borrower, - bytes32 salt, - bytes32 caveatHash - ) public view virtual returns (bytes memory) { - return - abi.encodePacked( - bytes1(0x19), - bytes1(0x01), - _DOMAIN_SEPARATOR, - keccak256( - abi.encode( - INTENT_ORIGINATION_TYPEHASH, - salt, - borrowerNonce[borrower], - caveatHash - ) - ) - ); - } - - function name() public pure override returns (string memory) { - return "Astaria Loan Manager"; - } - - function symbol() public pure override returns (string memory) { - return "ALM"; - } - - // MODIFIERS - modifier onlySeaport() { - if (msg.sender != address(seaport)) { - revert InvalidSender(); - } - _; - } - - // function active(Loan calldata loan) public view returns (bool) { - // return - // _getExtraData(uint256(keccak256(abi.encode(loan)))) == - // uint8(FieldFlags.ACTIVE); - // } - - function active(uint256 loanId) public view returns (bool) { - return _getExtraData(loanId) == uint8(FieldFlags.ACTIVE); - } - - function inactive(uint256 loanId) public view returns (bool) { - return _getExtraData(loanId) == uint8(FieldFlags.INACTIVE); - } - - function initialized(uint256 loanId) public view returns (bool) { - return _getExtraData(loanId) == uint8(FieldFlags.INITIALIZED); - } - - function tokenURI( - uint256 tokenId - ) public view override returns (string memory) { - return string(abi.encodePacked("https://astaria.xyz/loans?id=", tokenId)); - } - - function _issued(uint256 tokenId) internal view returns (bool) { - return (_getExtraData(tokenId) > uint8(0)); - } - - function issued(uint256 tokenId) external view returns (bool) { - return _issued(tokenId); - } - - // function getIssuer( - // Loan calldata loan - // ) external view returns (address payable) { - // uint256 loanId = uint256(keccak256(abi.encode(loan))); - // if (!_issued(loanId)) { - // revert InvalidLoan(loanId); - // } - // return !_exists(loanId) ? payable(loan.issuer) : payable(_ownerOf(loanId)); - // } - - //break the revert of the ownerOf method, so we can ensure anyone calling it in the settlement pipeline wont halt - function ownerOf(uint256 loanId) public view override returns (address) { - //not hasn't been issued but exists if we own it - return - _issued(loanId) && !_exists(loanId) ? address(this) : _ownerOf(loanId); - } - - function settle(Loan memory loan) external { - if (msg.sender != loan.custodian) { - revert InvalidSender(); - } - _settle(loan); - } - - function _settle(Loan memory loan) internal { - uint256 tokenId = loan.getId(); - if (!_issued(tokenId)) { - revert InvalidLoan(tokenId); - } - if (_exists(tokenId)) { - _burn(tokenId); - } - _setExtraData(tokenId, uint8(FieldFlags.INACTIVE)); - emit Close(tokenId); - } - - function _callCustody( - ReceivedItem[] calldata consideration, - bytes32[] calldata orderHashes, - uint256 contractNonce, - bytes calldata context - ) internal returns (bytes4 selector) { - address custodian; - - assembly { - custodian := calldataload(add(context.offset, 0x20)) // 0x20 offset for the first address 'custodian' - } - // Comparing the retrieved code hash with a known hash (placeholder here) - - bytes32 codeHash; - assembly { - codeHash := extcodehash(custodian) - } - if (codeHash != DEFAULT_CUSTODIAN_CODE_HASH) { - if ( - Custodian(custodian).custody( - consideration, - orderHashes, - contractNonce, - context - ) != Custodian.custody.selector - ) { - revert InvalidAction(); - } - } - } - - /** - * @dev previews the order for this contract offerer. - * - * @param caller The address of the contract fulfiller. - * @param fulfiller The address of the contract fulfiller. - * @param minimumReceived The minimum the fulfiller must receive. - * @param maximumSpent The most a fulfiller will spend - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function previewOrder( - address caller, - address fulfiller, - SpentItem[] calldata minimumReceived, - SpentItem[] calldata maximumSpent, - bytes calldata context // encoded based on the schemaID - ) - public - view - returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) - { - LoanManager.Obligation memory obligation = abi.decode( - context, - (LoanManager.Obligation) - ); - consideration = maximumSpent.toReceivedItems(obligation.custodian); - } - - /** - * @dev Gets the metadata for this contract offerer. - * - * @return name The name of the contract offerer. - * @return schemas The schemas supported by the contract offerer. - */ - function getSeaportMetadata() - external - pure - returns (string memory, Schema[] memory schemas) - { - schemas = new Schema[](1); - schemas[0] = Schema(8, ""); - return ("Loans", schemas); - } - - function _fillObligationAndVerify( - address fulfiller, - LoanManager.Obligation memory obligation, - SpentItem[] calldata maximumSpentFromBorrower - ) internal returns (SpentItem[] memory offer) { - address receiver = obligation.borrower; - bool enforceCaveats = fulfiller != receiver || - obligation.caveats.length > 0; - if (enforceCaveats) { - receiver = address(this); - } - Originator.Response memory response = Originator(obligation.originator) - .execute( - Originator.Request({ - custodian: obligation.custodian, - receiver: receiver, - collateral: maximumSpentFromBorrower, - debt: obligation.debt, - details: obligation.details, - signature: obligation.signature - }) - ); - Loan memory loan = Loan({ - start: block.timestamp, - issuer: response.issuer, - custodian: obligation.custodian, - borrower: obligation.borrower, - originator: obligation.originator, - collateral: maximumSpentFromBorrower, - debt: obligation.debt, - terms: response.terms - }); - // we settle via seaport channels if caveats are present - if (enforceCaveats) { - bytes32 caveatHash = keccak256( - encodeWithSaltAndBorrowerCounter( - obligation.borrower, - obligation.salt, - keccak256(abi.encode(obligation.caveats)) - ) - ); - //prevent replay on the hash - usedHashes[caveatHash] = true; - uint256 i = 0; - for (; i < obligation.caveats.length; ) { + using FixedPointMathLib for uint256; + using {StarPortLib.toReceivedItems} for SpentItem[]; + using {StarPortLib.getId} for LoanManager.Loan; + + ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); + // ConsiderationInterface public constant seaport = + // ConsiderationInterface(0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC); // mainnet + address public immutable defaultCustodian; + bytes32 public immutable DEFAULT_CUSTODIAN_CODE_HASH; + // uint256 public fee; + // uint256 private constant ONE_WORD = 0x20; + + // Define the EIP712 domain and typehash constants for generating signatures + bytes32 constant EIP_DOMAIN = keccak256("EIP712Domain(string version,uint256 chainId,address verifyingContract)"); + bytes32 public constant INTENT_ORIGINATION_TYPEHASH = + keccak256("IntentOrigination(bytes32 hash,bytes32 salt,uint256 nonce)"); + bytes32 constant VERSION = keccak256("0"); + + bytes32 internal immutable _DOMAIN_SEPARATOR; + + //TODO: we need to add in type hashes into the hashes + mapping(bytes32 => bool) public usedHashes; + mapping(address => uint256) public borrowerNonce; //needs to be invalidated + + enum FieldFlags { + INITIALIZED, + ACTIVE, + INACTIVE + } + + struct Terms { + address hook; //the address of the hookmodule + bytes hookData; //bytes encoded hook data + address pricing; //the address o the pricing module + bytes pricingData; //bytes encoded pricing data + address handler; //the address of the handler module + bytes handlerData; //bytes encoded handler data + } + + struct Loan { + uint256 start; //start of the loan + address custodian; //where the collateral is being held + address borrower; //the borrower + address issuer; //the capital issuer/lender + address originator; //who originated the loan + SpentItem[] collateral; //array of collateral + SpentItem[] debt; //array of debt + Terms terms; //the actionable terms of the loan + } + + struct Caveat { + address enforcer; + bytes terms; + } + + struct Obligation { + address custodian; + address originator; + address borrower; + bytes32 salt; + SpentItem[] debt; + Caveat[] caveats; + bytes details; + bytes signature; + } + + event Close(uint256 loanId); + event Open(uint256 loanId, LoanManager.Loan loan); + event SeaportCompatibleContractDeployed(); + + error ConduitTransferError(); + error InvalidConduit(); + error InvalidRefinance(); + error InvalidSender(); + error InvalidAction(); + error InvalidLoan(uint256); + error InvalidMaximumSpentEmpty(); + error InvalidDebtEmpty(); + error InvalidAmount(); + error InvalidDuration(); + error InvalidSignature(); + error InvalidOrigination(); + error InvalidSigner(); + error InvalidContext(ContextErrors); + error InvalidNoRefinanceConsideration(); + + enum ContextErrors { + BAD_ORIGINATION, + INVALID_PAYMENT, + LENGTH_MISMATCH, + BORROWER_MISMATCH, + COLLATERAL, + ZERO_ADDRESS, + INVALID_LOAN, + INVALID_CONDUIT, + INVALID_RESOLVER, + INVALID_COLLATERAL + } + + constructor() { + address custodian = address(new Custodian(this, address(seaport))); + + bytes32 defaultCustodianCodeHash; + assembly { + defaultCustodianCodeHash := extcodehash(custodian) + } + defaultCustodian = custodian; + DEFAULT_CUSTODIAN_CODE_HASH = defaultCustodianCodeHash; + _DOMAIN_SEPARATOR = keccak256(abi.encode(EIP_DOMAIN, VERSION, block.chainid, address(this))); + emit SeaportCompatibleContractDeployed(); + } + + // Encode the data with the account's nonce for generating a signature + function encodeWithSaltAndBorrowerCounter(address borrower, bytes32 salt, bytes32 caveatHash) + public + view + virtual + returns (bytes memory) + { + return abi.encodePacked( + bytes1(0x19), + bytes1(0x01), + _DOMAIN_SEPARATOR, + keccak256(abi.encode(INTENT_ORIGINATION_TYPEHASH, salt, borrowerNonce[borrower], caveatHash)) + ); + } + + function name() public pure override returns (string memory) { + return "Astaria Loan Manager"; + } + + function symbol() public pure override returns (string memory) { + return "ALM"; + } + + // MODIFIERS + modifier onlySeaport() { + if (msg.sender != address(seaport)) { + revert InvalidSender(); + } + _; + } + + // function active(Loan calldata loan) public view returns (bool) { + // return + // _getExtraData(uint256(keccak256(abi.encode(loan)))) == + // uint8(FieldFlags.ACTIVE); + // } + + function active(uint256 loanId) public view returns (bool) { + return _getExtraData(loanId) == uint8(FieldFlags.ACTIVE); + } + + function inactive(uint256 loanId) public view returns (bool) { + return _getExtraData(loanId) == uint8(FieldFlags.INACTIVE); + } + + function initialized(uint256 loanId) public view returns (bool) { + return _getExtraData(loanId) == uint8(FieldFlags.INITIALIZED); + } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + return string(abi.encodePacked("https://astaria.xyz/loans?id=", tokenId)); + } + + function _issued(uint256 tokenId) internal view returns (bool) { + return (_getExtraData(tokenId) > uint8(0)); + } + + function issued(uint256 tokenId) external view returns (bool) { + return _issued(tokenId); + } + + // function getIssuer( + // Loan calldata loan + // ) external view returns (address payable) { + // uint256 loanId = uint256(keccak256(abi.encode(loan))); + // if (!_issued(loanId)) { + // revert InvalidLoan(loanId); + // } + // return !_exists(loanId) ? payable(loan.issuer) : payable(_ownerOf(loanId)); + // } + + //break the revert of the ownerOf method, so we can ensure anyone calling it in the settlement pipeline wont halt + function ownerOf(uint256 loanId) public view override returns (address) { + //not hasn't been issued but exists if we own it + return _issued(loanId) && !_exists(loanId) ? address(this) : _ownerOf(loanId); + } + + function settle(Loan memory loan) external { + if (msg.sender != loan.custodian) { + revert InvalidSender(); + } + _settle(loan); + } + + function _settle(Loan memory loan) internal { + uint256 tokenId = loan.getId(); + if (!_issued(tokenId)) { + revert InvalidLoan(tokenId); + } + if (_exists(tokenId)) { + _burn(tokenId); + } + _setExtraData(tokenId, uint8(FieldFlags.INACTIVE)); + emit Close(tokenId); + } + + function _callCustody( + ReceivedItem[] calldata consideration, + bytes32[] calldata orderHashes, + uint256 contractNonce, + bytes calldata context + ) internal returns (bytes4 selector) { + address custodian; + + assembly { + custodian := calldataload(add(context.offset, 0x20)) // 0x20 offset for the first address 'custodian' + } + // Comparing the retrieved code hash with a known hash (placeholder here) + + bytes32 codeHash; + assembly { + codeHash := extcodehash(custodian) + } + if (codeHash != DEFAULT_CUSTODIAN_CODE_HASH) { + if ( + Custodian(custodian).custody(consideration, orderHashes, contractNonce, context) + != Custodian.custody.selector + ) { + revert InvalidAction(); + } + } + } + + /** + * @dev previews the order for this contract offerer. + * + * @param caller The address of the contract fulfiller. + * @param fulfiller The address of the contract fulfiller. + * @param minimumReceived The minimum the fulfiller must receive. + * @param maximumSpent The most a fulfiller will spend + * @param context The context of the order. + * @return offer The items spent by the order. + * @return consideration The items received by the order. + */ + function previewOrder( + address caller, + address fulfiller, + SpentItem[] calldata minimumReceived, + SpentItem[] calldata maximumSpent, + bytes calldata context // encoded based on the schemaID + ) public view returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { + LoanManager.Obligation memory obligation = abi.decode(context, (LoanManager.Obligation)); + consideration = maximumSpent.toReceivedItems(obligation.custodian); + } + + /** + * @dev Gets the metadata for this contract offerer. + * + * @return name The name of the contract offerer. + * @return schemas The schemas supported by the contract offerer. + */ + function getSeaportMetadata() external pure returns (string memory, Schema[] memory schemas) { + schemas = new Schema[](1); + schemas[0] = Schema(8, ""); + return ("Loans", schemas); + } + + function _fillObligationAndVerify( + address fulfiller, + LoanManager.Obligation memory obligation, + SpentItem[] calldata maximumSpentFromBorrower + ) internal returns (SpentItem[] memory offer) { + address receiver = obligation.borrower; + bool enforceCaveats = fulfiller != receiver || obligation.caveats.length > 0; + if (enforceCaveats) { + receiver = address(this); + } + Originator.Response memory response = Originator(obligation.originator).execute( + Originator.Request({ + custodian: obligation.custodian, + receiver: receiver, + collateral: maximumSpentFromBorrower, + debt: obligation.debt, + details: obligation.details, + signature: obligation.signature + }) + ); + Loan memory loan = Loan({ + start: block.timestamp, + issuer: response.issuer, + custodian: obligation.custodian, + borrower: obligation.borrower, + originator: obligation.originator, + collateral: maximumSpentFromBorrower, + debt: obligation.debt, + terms: response.terms + }); + // we settle via seaport channels if caveats are present + if (enforceCaveats) { + bytes32 caveatHash = keccak256( + encodeWithSaltAndBorrowerCounter( + obligation.borrower, obligation.salt, keccak256(abi.encode(obligation.caveats)) + ) + ); + //prevent replay on the hash + usedHashes[caveatHash] = true; + uint256 i = 0; + for (; i < obligation.caveats.length;) { + if (!CaveatEnforcer(obligation.caveats[i].enforcer).enforceCaveat(obligation.caveats[i].terms, loan)) { + revert InvalidOrigination(); + } + unchecked { + ++i; + } + } + offer = _setOffer(loan.debt, caveatHash); + } + + _issueLoanManager(loan, response.issuer.code.length > 0); + } + + function _issueLoanManager(Loan memory loan, bool mint) internal { + bytes memory encodedLoan = abi.encode(loan); + + uint256 loanId = loan.getId(); + + _setExtraData(loanId, uint8(FieldFlags.ACTIVE)); + if (mint) { + _safeMint(loan.issuer, loanId, encodedLoan); + } + emit Open(loanId, loan); + } + + /** + * @dev Generates the order for this contract offerer. + * + * @param fulfiller The address of the contract fulfiller. + * @param maximumSpent The maximum amount of items to be spent by the order. + * @param context The context of the order. + * @return offer The items spent by the order. + * @return consideration The items received by the order. + */ + function generateOrder( + address fulfiller, + SpentItem[] calldata minimumReceived, + SpentItem[] calldata maximumSpent, + bytes calldata context // encoded based on the schemaID + ) external onlySeaport returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { + LoanManager.Obligation memory obligation = abi.decode(context, (LoanManager.Obligation)); + consideration = maximumSpent.toReceivedItems(obligation.custodian); + + if (obligation.debt.length == 0) { + revert InvalidDebtEmpty(); + } + + if (maximumSpent.length == 0) { + revert InvalidMaximumSpentEmpty(); + } + offer = _fillObligationAndVerify(fulfiller, obligation, maximumSpent); + } + + function _setDebtApprovals(SpentItem memory debt) internal { + //approve consideration based on item type + if (debt.itemType != ItemType.ERC20) { + ERC721(debt.token).setApprovalForAll(address(seaport), true); + } else { + ERC20(debt.token).approve(address(seaport), debt.amount); + } + } + + function _setOffer(SpentItem[] memory debt, bytes32 caveatHash) internal returns (SpentItem[] memory offer) { + offer = new SpentItem[](debt.length + 1); + + for (uint256 i; i < debt.length;) { + offer[i] = debt[i]; + _setDebtApprovals(debt[i]); + unchecked { + ++i; + } + } + + offer[debt.length] = + SpentItem({itemType: ItemType.ERC721, token: address(this), identifier: uint256(caveatHash), amount: 1}); + } + + function transferFrom(address from, address to, uint256 tokenId) public payable override { + //active loans do nothing + if (from != address(this)) revert("cannot transfer loans"); + } + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) public payable override { + if (from != address(this)) revert("Cannot transfer loans"); + } + + /** + * @dev Generates the order for this contract offerer. + * + * @param offer The address of the contract fulfiller. + * @param consideration The maximum amount of items to be spent by the order. + * @param context The context of the order. + * @param orderHashes The context of the order. + * @param contractNonce The context of the order. + * @return ratifyOrderMagicValue The magic value returned by the ratify. + */ + function ratifyOrder( + SpentItem[] calldata offer, + ReceivedItem[] calldata consideration, + bytes calldata context, // encoded based on the schemaID + bytes32[] calldata orderHashes, + uint256 contractNonce + ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { + _callCustody(consideration, orderHashes, contractNonce, context); + ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC721, ContractOffererInterface) + returns (bool) + { + return interfaceId == type(ContractOffererInterface).interfaceId || interfaceId == type(ERC721).interfaceId + || super.supportsInterface(interfaceId); + } + + //TODO: needs tests + function refinance(LoanManager.Loan memory loan, bytes memory newPricingData, address conduit) external { + (,, address conduitController) = seaport.information(); + + if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) { + revert InvalidConduit(); + } + ( + // used to update the new loan amount + ReceivedItem[] memory considerationPayment, + // used to pay the carry amount + ReceivedItem[] memory carryPayment, + // note: considerationPayment - carryPayment = amount to pay lender + + // used for any additional payments beyond consideration and carry + ReceivedItem[] memory additionalPayment + ) = Pricing(loan.terms.pricing).isValidRefinance(loan, newPricingData, msg.sender); + + ReceivedItem[] memory refinanceConsideration = + _mergeConsiderations(considerationPayment, carryPayment, additionalPayment); + refinanceConsideration = _removeZeroAmounts(refinanceConsideration); + + // if for malicious or non-malicious the refinanceConsideration is zero + if (refinanceConsideration.length == 0) { + revert InvalidNoRefinanceConsideration(); + } + + _settle(loan); + + for (uint256 i; i < loan.debt.length;) { + loan.debt[i].amount = considerationPayment[i].amount; + unchecked { + ++i; + } + } + if ( - !CaveatEnforcer(obligation.caveats[i].enforcer).enforceCaveat( - obligation.caveats[i].terms, - loan - ) + ConduitInterface(conduit).execute(_packageTransfers(refinanceConsideration, msg.sender)) + != ConduitInterface.execute.selector ) { - revert InvalidOrigination(); + revert ConduitTransferError(); } - unchecked { - ++i; - } - } - offer = _setOffer(loan.debt, caveatHash); - } - - _issueLoanManager(loan, response.issuer.code.length > 0); - } - - function _issueLoanManager(Loan memory loan, bool mint) internal { - bytes memory encodedLoan = abi.encode(loan); - - uint256 loanId = loan.getId(); - - _setExtraData(loanId, uint8(FieldFlags.ACTIVE)); - if (mint) { - _safeMint(loan.issuer, loanId, encodedLoan); - } - emit Open(loanId, loan); - } - - /** - * @dev Generates the order for this contract offerer. - * - * @param fulfiller The address of the contract fulfiller. - * @param maximumSpent The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @return offer The items spent by the order. - * @return consideration The items received by the order. - */ - function generateOrder( - address fulfiller, - SpentItem[] calldata minimumReceived, - SpentItem[] calldata maximumSpent, - bytes calldata context // encoded based on the schemaID - ) - external - onlySeaport - returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) - { - LoanManager.Obligation memory obligation = abi.decode( - context, - (LoanManager.Obligation) - ); - consideration = maximumSpent.toReceivedItems(obligation.custodian); - - if (obligation.debt.length == 0) { - revert InvalidDebtEmpty(); - } - - if (maximumSpent.length == 0) { - revert InvalidMaximumSpentEmpty(); - } - offer = _fillObligationAndVerify(fulfiller, obligation, maximumSpent); - } - - function _setDebtApprovals(SpentItem memory debt) internal { - //approve consideration based on item type - if (debt.itemType != ItemType.ERC20) { - ERC721(debt.token).setApprovalForAll(address(seaport), true); - } else { - ERC20(debt.token).approve(address(seaport), debt.amount); - } - } - - function _setOffer( - SpentItem[] memory debt, - bytes32 caveatHash - ) internal returns (SpentItem[] memory offer) { - offer = new SpentItem[](debt.length + 1); - - for (uint256 i; i < debt.length; ) { - offer[i] = debt[i]; - _setDebtApprovals(debt[i]); - unchecked { - ++i; - } - } - - offer[debt.length] = SpentItem({ - itemType: ItemType.ERC721, - token: address(this), - identifier: uint256(caveatHash), - amount: 1 - }); - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public payable override { - //active loans do nothing - if (from != address(this)) revert("cannot transfer loans"); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) public payable override { - if (from != address(this)) revert("Cannot transfer loans"); - } - - /** - * @dev Generates the order for this contract offerer. - * - * @param offer The address of the contract fulfiller. - * @param consideration The maximum amount of items to be spent by the order. - * @param context The context of the order. - * @param orderHashes The context of the order. - * @param contractNonce The context of the order. - * @return ratifyOrderMagicValue The magic value returned by the ratify. - */ - function ratifyOrder( - SpentItem[] calldata offer, - ReceivedItem[] calldata consideration, - bytes calldata context, // encoded based on the schemaID - bytes32[] calldata orderHashes, - uint256 contractNonce - ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { - _callCustody(consideration, orderHashes, contractNonce, context); - ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; - } - - function supportsInterface( - bytes4 interfaceId - ) - public - view - virtual - override(ERC721, ContractOffererInterface) - returns (bool) - { - return - interfaceId == type(ContractOffererInterface).interfaceId || - interfaceId == type(ERC721).interfaceId || - super.supportsInterface(interfaceId); - } - - //TODO: needs tests - function refinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address conduit - ) external { - (, , address conduitController) = seaport.information(); - - if ( - ConduitControllerInterface(conduitController).ownerOf(conduit) != - msg.sender - ) { - revert InvalidConduit(); - } - ( - // used to update the new loan amount - ReceivedItem[] memory considerationPayment, - // used to pay the carry amount - ReceivedItem[] memory carryPayment, - // note: considerationPayment - carryPayment = amount to pay lender - - // used for any additional payments beyond consideration and carry - ReceivedItem[] memory additionalPayment - ) = Pricing(loan.terms.pricing).isValidRefinance( - loan, - newPricingData, - msg.sender - ); - - ReceivedItem[] memory refinanceConsideration = _mergeConsiderations( - considerationPayment, - carryPayment, - additionalPayment - ); - refinanceConsideration = _removeZeroAmounts(refinanceConsideration); - - // if for malicious or non-malicious the refinanceConsideration is zero - if (refinanceConsideration.length == 0) { - revert InvalidNoRefinanceConsideration(); - } - - _settle(loan); - - for (uint256 i; i < loan.debt.length; ) { - loan.debt[i].amount = considerationPayment[i].amount; - unchecked { - ++i; - } - } - - if ( - ConduitInterface(conduit).execute( - _packageTransfers(refinanceConsideration, msg.sender) - ) != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); - } - - loan.terms.pricingData = newPricingData; - loan.originator = msg.sender; - loan.issuer = msg.sender; - loan.start = block.timestamp; - _issueLoanManager(loan, msg.sender.code.length > 0); - } + + loan.terms.pricingData = newPricingData; + loan.originator = msg.sender; + loan.issuer = msg.sender; + loan.start = block.timestamp; + _issueLoanManager(loan, msg.sender.code.length > 0); + } } diff --git a/src/custodians/LazyMintCustodian.sol b/src/custodians/LazyMintCustodian.sol index 8c85df9a..55d819e1 100644 --- a/src/custodians/LazyMintCustodian.sol +++ b/src/custodians/LazyMintCustodian.sol @@ -4,56 +4,39 @@ import "../Custodian.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; contract LazyMintCustodian is Custodian, ERC721 { - constructor(LoanManager LM_, address seaport_) Custodian(LM_, seaport_) {} - - function _getBorrower( - LoanManager.Loan memory loan - ) internal view override returns (address) { - uint256 loanId = uint256(keccak256(abi.encode(loan))); - return _exists(loanId) ? ownerOf(loanId) : loan.borrower; - } - - function mint(LoanManager.Loan calldata loan) external { - bytes memory encodedLoan = abi.encode(loan); - uint256 loanId = uint256(keccak256(encodedLoan)); - _safeMint(loan.issuer, loanId, encodedLoan); - } - - function tokenURI( - uint256 tokenId - ) public view override returns (string memory) { - return - string( - abi.encodePacked( - "https://astaria.xyz/custodian/", - address(this), - string("/"), - tokenId - ) - ); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(ERC721, Custodian) returns (bool) { - return - ERC721.supportsInterface(interfaceId) || - // interfaceId == type(this).interfaceId || - // interfaceId == type(LazyMintCustodian).interfaceId || - super.supportsInterface(interfaceId); - } - - function _beforeSettleLoanHook( - LoanManager.Loan memory loan - ) internal override { - _burn(uint256(keccak256(abi.encode(loan)))); - } - - function name() public pure override returns (string memory) { - return "Astaria Custodian Token"; - } - - function symbol() public pure override returns (string memory) { - return "ACT"; - } + constructor(LoanManager LM_, address seaport_) Custodian(LM_, seaport_) {} + + function _getBorrower(LoanManager.Loan memory loan) internal view override returns (address) { + uint256 loanId = uint256(keccak256(abi.encode(loan))); + return _exists(loanId) ? ownerOf(loanId) : loan.borrower; + } + + function mint(LoanManager.Loan calldata loan) external { + bytes memory encodedLoan = abi.encode(loan); + uint256 loanId = uint256(keccak256(encodedLoan)); + _safeMint(loan.issuer, loanId, encodedLoan); + } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + return string(abi.encodePacked("https://astaria.xyz/custodian/", address(this), string("/"), tokenId)); + } + + function supportsInterface(bytes4 interfaceId) public view override(ERC721, Custodian) returns (bool) { + return ERC721.supportsInterface(interfaceId) + // interfaceId == type(this).interfaceId || + // interfaceId == type(LazyMintCustodian).interfaceId || + || super.supportsInterface(interfaceId); + } + + function _beforeSettleLoanHook(LoanManager.Loan memory loan) internal override { + _burn(uint256(keccak256(abi.encode(loan)))); + } + + function name() public pure override returns (string memory) { + return "Astaria Custodian Token"; + } + + function symbol() public pure override returns (string memory) { + return "ACT"; + } } diff --git a/src/enforcers/CaveatEnforcer.sol b/src/enforcers/CaveatEnforcer.sol index 6064f75e..63110ec6 100644 --- a/src/enforcers/CaveatEnforcer.sol +++ b/src/enforcers/CaveatEnforcer.sol @@ -3,8 +3,5 @@ pragma solidity =0.8.17; import {LoanManager} from "src/LoanManager.sol"; abstract contract CaveatEnforcer { - function enforceCaveat( - bytes calldata terms, - LoanManager.Loan memory loan - ) public virtual returns (bool); + function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) public virtual returns (bool); } diff --git a/src/enforcers/CollateralEnfocer.sol b/src/enforcers/CollateralEnfocer.sol index c74d633b..543b9a3d 100644 --- a/src/enforcers/CollateralEnfocer.sol +++ b/src/enforcers/CollateralEnfocer.sol @@ -3,25 +3,23 @@ pragma solidity =0.8.17; import {CaveatEnforcer} from "./CaveatEnforcer.sol"; import {LoanManager} from "src/LoanManager.sol"; -import { - ItemType, - SpentItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; contract CollateralEnforcer is CaveatEnforcer { - struct Details { - SpentItem[] collateral; - bool isAny; - } + struct Details { + SpentItem[] collateral; + bool isAny; + } - function enforceCaveat( - bytes calldata terms, - LoanManager.Loan memory loan - ) public view override returns (bool valid) { - Details memory details = abi.decode(terms, (Details)); - //TODO: figure out or/and comparison simple impl + function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) + public + view + override + returns (bool valid) + { + Details memory details = abi.decode(terms, (Details)); + //TODO: figure out or/and comparison simple impl - return (keccak256(abi.encode(loan.collateral)) == - keccak256(abi.encode(details.collateral))); - } + return (keccak256(abi.encode(loan.collateral)) == keccak256(abi.encode(details.collateral))); + } } diff --git a/src/enforcers/RateEnforcer.sol b/src/enforcers/RateEnforcer.sol index 938f1db6..773aec9e 100644 --- a/src/enforcers/RateEnforcer.sol +++ b/src/enforcers/RateEnforcer.sol @@ -5,23 +5,19 @@ import {BasePricing} from "src/pricing/BasePricing.sol"; import {LoanManager} from "src/LoanManager.sol"; contract FixedRateEnforcer is CaveatEnforcer { - struct Details { - uint256 maxRate; - uint256 maxCarryRate; - } + struct Details { + uint256 maxRate; + uint256 maxCarryRate; + } - function enforceCaveat( - bytes calldata caveatTerms, //enforce theis - LoanManager.Loan memory loan - ) public view override returns (bool) { - //lower and upper bounds - Details memory caveatDetails = abi.decode(caveatTerms, (Details)); + function enforceCaveat( + bytes calldata caveatTerms, //enforce theis + LoanManager.Loan memory loan + ) public view override returns (bool) { + //lower and upper bounds + Details memory caveatDetails = abi.decode(caveatTerms, (Details)); - BasePricing.Details memory details = abi.decode( - loan.terms.pricingData, - (BasePricing.Details) - ); - return (caveatDetails.maxRate > details.rate && - caveatDetails.maxCarryRate > details.carryRate); - } + BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + return (caveatDetails.maxRate > details.rate && caveatDetails.maxCarryRate > details.carryRate); + } } diff --git a/src/enforcers/TermEnforcer.sol b/src/enforcers/TermEnforcer.sol index abc0dcbe..3f98b224 100644 --- a/src/enforcers/TermEnforcer.sol +++ b/src/enforcers/TermEnforcer.sol @@ -5,28 +5,30 @@ import {LoanManager} from "src/LoanManager.sol"; import "forge-std/console.sol"; contract TermEnforcer is CaveatEnforcer { - struct Details { - address pricing; - address hook; - address handler; - } + struct Details { + address pricing; + address hook; + address handler; + } - function enforceCaveat( - bytes calldata terms, - LoanManager.Loan memory loan - ) public view override returns (bool valid) { - Details memory details = abi.decode(terms, (Details)); - valid = true; + function enforceCaveat(bytes calldata terms, LoanManager.Loan memory loan) + public + view + override + returns (bool valid) + { + Details memory details = abi.decode(terms, (Details)); + valid = true; - console.log("enforcing term caveat"); - if (details.pricing != address(0)) { - valid = valid && loan.terms.pricing == details.pricing; - } - if (details.hook != address(0)) { - valid = valid && loan.terms.hook == details.hook; - } - if (details.handler != address(0)) { - valid = valid && loan.terms.handler == details.handler; + console.log("enforcing term caveat"); + if (details.pricing != address(0)) { + valid = valid && loan.terms.pricing == details.pricing; + } + if (details.hook != address(0)) { + valid = valid && loan.terms.hook == details.hook; + } + if (details.handler != address(0)) { + valid = valid && loan.terms.handler == details.handler; + } } - } } diff --git a/src/handlers/AstariaV1SettlementHandler.sol b/src/handlers/AstariaV1SettlementHandler.sol index 3d707aa3..4c2244bd 100644 --- a/src/handlers/AstariaV1SettlementHandler.sol +++ b/src/handlers/AstariaV1SettlementHandler.sol @@ -1,42 +1,33 @@ pragma solidity =0.8.17; -import { - LoanManager, - SpentItem, - ReceivedItem, - SettlementHandler -} from "src/handlers/SettlementHandler.sol"; +import {LoanManager, SpentItem, ReceivedItem, SettlementHandler} from "src/handlers/SettlementHandler.sol"; import {BaseHook} from "src/hooks/BaseHook.sol"; import {BaseRecall} from "src/hooks/BaseRecall.sol"; import {DutchAuctionHandler} from "src/handlers/DutchAuctionHandler.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; contract AstariaV1SettlementHandler is DutchAuctionHandler { - using {StarPortLib.getId} for LoanManager.Loan; + using {StarPortLib.getId} for LoanManager.Loan; - constructor(LoanManager LM_) DutchAuctionHandler(LM_) {} + constructor(LoanManager LM_) DutchAuctionHandler(LM_) {} - function getSettlement( - LoanManager.Loan memory loan - ) - external - view - virtual - override - returns (ReceivedItem[] memory, address restricted) - { - (address recaller, ) = BaseRecall(loan.terms.hook).recalls(loan.getId()); + function getSettlement(LoanManager.Loan memory loan) + external + view + virtual + override + returns (ReceivedItem[] memory, address restricted) + { + (address recaller,) = BaseRecall(loan.terms.hook).recalls(loan.getId()); - if (recaller == loan.issuer) { - return (new ReceivedItem[](0), recaller); - } else { - return DutchAuctionHandler(address(this)).getSettlement(loan); + if (recaller == loan.issuer) { + return (new ReceivedItem[](0), recaller); + } else { + return DutchAuctionHandler(address(this)).getSettlement(loan); + } } - } - function validate( - LoanManager.Loan calldata loan - ) external view virtual override returns (bool) { - return true; - } + function validate(LoanManager.Loan calldata loan) external view virtual override returns (bool) { + return true; + } } diff --git a/src/handlers/DutchAuctionHandler.sol b/src/handlers/DutchAuctionHandler.sol index d3ed4daa..a3a40a2e 100644 --- a/src/handlers/DutchAuctionHandler.sol +++ b/src/handlers/DutchAuctionHandler.sol @@ -1,100 +1,87 @@ pragma solidity =0.8.17; import { - ItemType, - OfferItem, - SpentItem, - ReceivedItem, - OrderParameters + ItemType, + OfferItem, + SpentItem, + ReceivedItem, + OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Originator} from "src/originators/Originator.sol"; import {Pricing} from "src/pricing/Pricing.sol"; import {AmountDeriver} from "seaport-core/src/lib/AmountDeriver.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; -import { - LoanManager, - SettlementHandler -} from "src/handlers/SettlementHandler.sol"; +import {LoanManager, SettlementHandler} from "src/handlers/SettlementHandler.sol"; import {ConduitHelper} from "src/ConduitHelper.sol"; import "forge-std/console.sol"; -contract DutchAuctionHandler is - SettlementHandler, - AmountDeriver, - ConduitHelper -{ - constructor(LoanManager LM_) SettlementHandler(LM_) { - LM = LM_; - } +contract DutchAuctionHandler is SettlementHandler, AmountDeriver, ConduitHelper { + constructor(LoanManager LM_) SettlementHandler(LM_) { + LM = LM_; + } - using FixedPointMathLib for uint256; + using FixedPointMathLib for uint256; - error InvalidAmount(); + error InvalidAmount(); - struct Details { - uint256 startingPrice; - uint256 endingPrice; - uint256 window; - } + struct Details { + uint256 startingPrice; + uint256 endingPrice; + uint256 window; + } - function getSettlement( - LoanManager.Loan memory loan - ) - external - view - virtual - override - returns (ReceivedItem[] memory consideration, address restricted) - { - Details memory details = abi.decode(loan.terms.handlerData, (Details)); - uint256 settlementPrice; + function getSettlement(LoanManager.Loan memory loan) + external + view + virtual + override + returns (ReceivedItem[] memory consideration, address restricted) + { + Details memory details = abi.decode(loan.terms.handlerData, (Details)); + uint256 settlementPrice; - settlementPrice = _locateCurrentAmount({ - startAmount: details.startingPrice, - endAmount: details.endingPrice, - startTime: block.timestamp, - endTime: block.timestamp + details.window, - roundUp: true - }); + settlementPrice = _locateCurrentAmount({ + startAmount: details.startingPrice, + endAmount: details.endingPrice, + startTime: block.timestamp, + endTime: block.timestamp + details.window, + roundUp: true + }); - ( - ReceivedItem[] memory paymentConsiderations, - ReceivedItem[] memory carryFeeConsideration - ) = Pricing(loan.terms.pricing).getPaymentConsideration(loan); + (ReceivedItem[] memory paymentConsiderations, ReceivedItem[] memory carryFeeConsideration) = + Pricing(loan.terms.pricing).getPaymentConsideration(loan); - consideration = new ReceivedItem[]( + consideration = new ReceivedItem[]( paymentConsiderations.length + carryFeeConsideration.length ); - //loop the payment considerations and add them to the consideration array + //loop the payment considerations and add them to the consideration array - uint256 i = 0; - for (; i < paymentConsiderations.length; ) { - consideration[i] = paymentConsiderations[i]; - unchecked { - ++i; - } + uint256 i = 0; + for (; i < paymentConsiderations.length;) { + consideration[i] = paymentConsiderations[i]; + unchecked { + ++i; + } + } + uint256 j = 0; + i = paymentConsiderations.length; + //loop fee considerations and add them to the consideration array + for (; j < carryFeeConsideration.length;) { + if (carryFeeConsideration[j].amount > 0) { + consideration[i + j] = carryFeeConsideration[j]; + } + unchecked { + ++j; + } + } + consideration = _removeZeroAmounts(consideration); } - uint256 j = 0; - i = paymentConsiderations.length; - //loop fee considerations and add them to the consideration array - for (; j < carryFeeConsideration.length; ) { - if (carryFeeConsideration[j].amount > 0) { - consideration[i + j] = carryFeeConsideration[j]; - } - unchecked { - ++j; - } - } - consideration = _removeZeroAmounts(consideration); - } - function validate( - LoanManager.Loan calldata loan - ) external view virtual override returns (bool) { - Details memory details = abi.decode(loan.terms.handlerData, (Details)); - return details.startingPrice > details.endingPrice; - } + function validate(LoanManager.Loan calldata loan) external view virtual override returns (bool) { + Details memory details = abi.decode(loan.terms.handlerData, (Details)); + return details.startingPrice > details.endingPrice; + } } diff --git a/src/handlers/EnglishAuctionHandler.sol b/src/handlers/EnglishAuctionHandler.sol index 71038d31..07bb77bc 100644 --- a/src/handlers/EnglishAuctionHandler.sol +++ b/src/handlers/EnglishAuctionHandler.sol @@ -1,195 +1,171 @@ pragma solidity =0.8.17; import {LoanManager} from "src/LoanManager.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; +import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; +import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; import { - ConduitTransfer, - ConduitItemType -} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; -import { - ConsiderationInterface -} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; -import { - ConduitInterface -} from "seaport-types/src/interfaces/ConduitInterface.sol"; -import { - ConduitControllerInterface -} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; -import { - ItemType, - OfferItem, - Schema, - SpentItem, - ReceivedItem, - OrderParameters + ItemType, + OfferItem, + Schema, + SpentItem, + ReceivedItem, + OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {SettlementHandler} from "src/handlers/SettlementHandler.sol"; import {Consideration} from "seaport-core/src/lib/Consideration.sol"; import { - ConsiderationItem, - AdvancedOrder, - Order, - CriteriaResolver, - OrderType + ConsiderationItem, + AdvancedOrder, + Order, + CriteriaResolver, + OrderType } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Pricing} from "src/pricing/Pricing.sol"; contract EnglishAuctionHandler is SettlementHandler { - using FixedPointMathLib for uint256; - struct Details { - uint256[] reservePrice; // per debt item - uint256 window; - } - - ConsiderationInterface consideration; - address public ENGLISH_AUCTION_ZONE; - // address public constant ENGLISH_AUCTION_ZONE = 0x110b2B128A9eD1be5Ef3232D8e4E41640dF5c2Cd; - - // opensea payment receiver - - address payable public constant OS_RECEIVER = - payable(0x0000a26b00c1F0DF003000390027140000fAa719); - - error InvalidOrder(); - - constructor( - LoanManager LM_, - ConsiderationInterface consideration_, - address EAZone_ - ) SettlementHandler(LM_) { - consideration = consideration_; - ENGLISH_AUCTION_ZONE = EAZone_; - } - - //use when building offers to ensure the data works with the handler - function validate( - LoanManager.Loan calldata loan - ) external view override returns (bool) { - Details memory details = abi.decode(loan.terms.handlerData, (Details)); - return details.reservePrice.length == loan.debt.length; - } - - function execute( - LoanManager.Loan calldata loan - ) external virtual override returns (bytes4 selector) { - selector = SettlementHandler.execute.selector; - } - - function getSettlement( - LoanManager.Loan memory loan - ) - external - view - override - returns (ReceivedItem[] memory consideration, address restricted) - { - return (new ReceivedItem[](0), address(this)); - } - - function liquidate(LoanManager.Loan calldata loan) external { - OrderParameters memory op = OrderParameters({ - offerer: address(loan.custodian), - zone: address(0), - offer: new OfferItem[](0), - consideration: new ConsiderationItem[](0), - orderType: OrderType.CONTRACT, - startTime: block.timestamp, - endTime: block.timestamp + 100, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 0 - }); - - AdvancedOrder memory x = AdvancedOrder({ - parameters: op, - numerator: 1, - denominator: 1, - signature: "0x", - extraData: abi.encode(loan) - }); - - consideration.fulfillAdvancedOrder({ - advancedOrder: x, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(0) - }); - - Details memory details = abi.decode(loan.terms.handlerData, (Details)); - //check the loan debt type and set the order type based on that - - OfferItem[] memory offerItems = new OfferItem[](loan.collateral.length); - //convert offer items from spent items - uint256 i = 0; - for (; i < loan.collateral.length; ) { - offerItems[i] = OfferItem({ - itemType: loan.collateral[i].itemType, - token: loan.collateral[i].token, - identifierOrCriteria: loan.collateral[i].identifier, - startAmount: loan.collateral[i].amount, - endAmount: loan.collateral[i].amount - }); - unchecked { - ++i; - } + using FixedPointMathLib for uint256; + + struct Details { + uint256[] reservePrice; // per debt item + uint256 window; } - ConsiderationItem[] memory considerations = new ConsiderationItem[]( - loan.debt.length + 1 - ); - uint256 feeRake = uint256(25000000000000000); - uint256 reserveRake; - uint256 rake; - - i = 0; - for (; i < loan.debt.length; ) { - rake = (loan.debt[i].amount * 2).mulWad(feeRake); - reserveRake = details.reservePrice[i].mulWad(feeRake); - considerations[i] = ConsiderationItem({ - itemType: loan.debt[i].itemType, - token: loan.debt[i].token, - identifierOrCriteria: loan.debt[i].identifier, - startAmount: details.reservePrice[i] - reserveRake, - endAmount: (loan.debt[i].amount * 2) - rake, - recipient: payable(loan.issuer) - }); - unchecked { - ++i; - } + ConsiderationInterface consideration; + address public ENGLISH_AUCTION_ZONE; + // address public constant ENGLISH_AUCTION_ZONE = 0x110b2B128A9eD1be5Ef3232D8e4E41640dF5c2Cd; + + // opensea payment receiver + + address payable public constant OS_RECEIVER = payable(0x0000a26b00c1F0DF003000390027140000fAa719); + + error InvalidOrder(); + + constructor(LoanManager LM_, ConsiderationInterface consideration_, address EAZone_) SettlementHandler(LM_) { + consideration = consideration_; + ENGLISH_AUCTION_ZONE = EAZone_; + } + + //use when building offers to ensure the data works with the handler + function validate(LoanManager.Loan calldata loan) external view override returns (bool) { + Details memory details = abi.decode(loan.terms.handlerData, (Details)); + return details.reservePrice.length == loan.debt.length; + } + + function execute(LoanManager.Loan calldata loan) external virtual override returns (bytes4 selector) { + selector = SettlementHandler.execute.selector; } - considerations[considerations.length - 1] = ConsiderationItem({ - itemType: loan.debt[0].itemType, - token: loan.debt[0].token, - identifierOrCriteria: loan.debt[0].identifier, - startAmount: reserveRake, - endAmount: rake, - recipient: OS_RECEIVER - }); - - op = OrderParameters({ - offerer: address(this), - zone: ENGLISH_AUCTION_ZONE, - offer: offerItems, - consideration: considerations, - orderType: OrderType.FULL_RESTRICTED, - startTime: block.timestamp + 1, - endTime: block.timestamp + details.window, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 2 - }); - - Order memory order = Order({parameters: op, signature: "0x"}); - - Order[] memory orders = new Order[](1); - orders[0] = order; - bool isValid = consideration.validate(orders); - if (!isValid) { - revert InvalidOrder(); + function getSettlement(LoanManager.Loan memory loan) + external + view + override + returns (ReceivedItem[] memory consideration, address restricted) + { + return (new ReceivedItem[](0), address(this)); + } + + function liquidate(LoanManager.Loan calldata loan) external { + OrderParameters memory op = OrderParameters({ + offerer: address(loan.custodian), + zone: address(0), + offer: new OfferItem[](0), + consideration: new ConsiderationItem[](0), + orderType: OrderType.CONTRACT, + startTime: block.timestamp, + endTime: block.timestamp + 100, + zoneHash: bytes32(0), + salt: 0, + conduitKey: bytes32(0), + totalOriginalConsiderationItems: 0 + }); + + AdvancedOrder memory x = + AdvancedOrder({parameters: op, numerator: 1, denominator: 1, signature: "0x", extraData: abi.encode(loan)}); + + consideration.fulfillAdvancedOrder({ + advancedOrder: x, + criteriaResolvers: new CriteriaResolver[](0), + fulfillerConduitKey: bytes32(0), + recipient: address(0) + }); + + Details memory details = abi.decode(loan.terms.handlerData, (Details)); + //check the loan debt type and set the order type based on that + + OfferItem[] memory offerItems = new OfferItem[](loan.collateral.length); + //convert offer items from spent items + uint256 i = 0; + for (; i < loan.collateral.length;) { + offerItems[i] = OfferItem({ + itemType: loan.collateral[i].itemType, + token: loan.collateral[i].token, + identifierOrCriteria: loan.collateral[i].identifier, + startAmount: loan.collateral[i].amount, + endAmount: loan.collateral[i].amount + }); + unchecked { + ++i; + } + } + ConsiderationItem[] memory considerations = new ConsiderationItem[]( + loan.debt.length + 1 + ); + + uint256 feeRake = uint256(25000000000000000); + uint256 reserveRake; + uint256 rake; + + i = 0; + for (; i < loan.debt.length;) { + rake = (loan.debt[i].amount * 2).mulWad(feeRake); + reserveRake = details.reservePrice[i].mulWad(feeRake); + considerations[i] = ConsiderationItem({ + itemType: loan.debt[i].itemType, + token: loan.debt[i].token, + identifierOrCriteria: loan.debt[i].identifier, + startAmount: details.reservePrice[i] - reserveRake, + endAmount: (loan.debt[i].amount * 2) - rake, + recipient: payable(loan.issuer) + }); + unchecked { + ++i; + } + } + + considerations[considerations.length - 1] = ConsiderationItem({ + itemType: loan.debt[0].itemType, + token: loan.debt[0].token, + identifierOrCriteria: loan.debt[0].identifier, + startAmount: reserveRake, + endAmount: rake, + recipient: OS_RECEIVER + }); + + op = OrderParameters({ + offerer: address(this), + zone: ENGLISH_AUCTION_ZONE, + offer: offerItems, + consideration: considerations, + orderType: OrderType.FULL_RESTRICTED, + startTime: block.timestamp + 1, + endTime: block.timestamp + details.window, + zoneHash: bytes32(0), + salt: 0, + conduitKey: bytes32(0), + totalOriginalConsiderationItems: 2 + }); + + Order memory order = Order({parameters: op, signature: "0x"}); + + Order[] memory orders = new Order[](1); + orders[0] = order; + bool isValid = consideration.validate(orders); + if (!isValid) { + revert InvalidOrder(); + } } - } } diff --git a/src/handlers/SettlementHandler.sol b/src/handlers/SettlementHandler.sol index 33b96a6f..42149ced 100644 --- a/src/handlers/SettlementHandler.sol +++ b/src/handlers/SettlementHandler.sol @@ -2,36 +2,27 @@ pragma solidity =0.8.17; import {LoanManager} from "src/LoanManager.sol"; -import { - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; abstract contract SettlementHandler { - LoanManager LM; + LoanManager LM; - constructor(LoanManager LM_) { - LM = LM_; - } + constructor(LoanManager LM_) { + LM = LM_; + } - function execute( - LoanManager.Loan calldata loan - ) external virtual returns (bytes4) { - return SettlementHandler.execute.selector; - } + function execute(LoanManager.Loan calldata loan) external virtual returns (bytes4) { + return SettlementHandler.execute.selector; + } - function validate( - LoanManager.Loan calldata loan - ) external view virtual returns (bool); + function validate(LoanManager.Loan calldata loan) external view virtual returns (bool); - function getSettlement( - LoanManager.Loan memory loan - ) - external - virtual - returns ( - // view - ReceivedItem[] memory consideration, - address restricted - ); + function getSettlement(LoanManager.Loan memory loan) + external + virtual + returns ( + // view + ReceivedItem[] memory consideration, + address restricted + ); } diff --git a/src/hooks/AstariaV1SettlementHook.sol b/src/hooks/AstariaV1SettlementHook.sol index 29c6b7b4..d83a9f68 100644 --- a/src/hooks/AstariaV1SettlementHook.sol +++ b/src/hooks/AstariaV1SettlementHook.sol @@ -7,28 +7,20 @@ import {BaseHook} from "src/hooks/BaseHook.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; contract AstariaV1SettlementHook is BaseHook, BaseRecall { - using {StarPortLib.getId} for LoanManager.Loan; + using {StarPortLib.getId} for LoanManager.Loan; - constructor(LoanManager LM_) BaseRecall(LM_) {} + constructor(LoanManager LM_) BaseRecall(LM_) {} - function isActive( - LoanManager.Loan calldata loan - ) external view override returns (bool) { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - uint256 tokenId = loan.getId(); - return - !(uint256(recalls[tokenId].start) + details.recallWindow > - block.timestamp); - } + function isActive(LoanManager.Loan calldata loan) external view override returns (bool) { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + uint256 tokenId = loan.getId(); + return !(uint256(recalls[tokenId].start) + details.recallWindow > block.timestamp); + } - function isRecalled( - LoanManager.Loan calldata loan - ) external view override returns (bool) { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - uint256 tokenId = loan.getId(); - Recall memory recall = recalls[tokenId]; - return - (recall.start + details.recallWindow > block.timestamp) && - recall.start != 0; - } + function isRecalled(LoanManager.Loan calldata loan) external view override returns (bool) { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + uint256 tokenId = loan.getId(); + Recall memory recall = recalls[tokenId]; + return (recall.start + details.recallWindow > block.timestamp) && recall.start != 0; + } } diff --git a/src/hooks/BaseHook.sol b/src/hooks/BaseHook.sol index fa5fb5b8..37fa6543 100644 --- a/src/hooks/BaseHook.sol +++ b/src/hooks/BaseHook.sol @@ -4,7 +4,5 @@ import {SettlementHook} from "src/hooks/SettlementHook.sol"; import {LoanManager} from "src/LoanManager.sol"; abstract contract BaseHook is SettlementHook { - function isRecalled( - LoanManager.Loan calldata loan - ) external view virtual returns (bool); + function isRecalled(LoanManager.Loan calldata loan) external view virtual returns (bool); } diff --git a/src/hooks/BaseRecall.sol b/src/hooks/BaseRecall.sol index 86a1be7c..de213057 100644 --- a/src/hooks/BaseRecall.sol +++ b/src/hooks/BaseRecall.sol @@ -13,219 +13,174 @@ import {ConduitHelper} from "src/ConduitHelper.sol"; import {ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {ItemType} from "seaport-types/src/lib/ConsiderationEnums.sol"; -import { - ConduitControllerInterface -} from "seaport-sol/src/ConduitControllerInterface.sol"; +import {ConduitControllerInterface} from "seaport-sol/src/ConduitControllerInterface.sol"; -import { - ConsiderationInterface -} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; -import { - ConduitInterface -} from "seaport-types/src/interfaces/ConduitInterface.sol"; +import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; abstract contract BaseRecall is ConduitHelper { - using FixedPointMathLib for uint256; - using {StarPortLib.getId} for LoanManager.Loan; - event Recalled(uint256 loandId, address recaller, uint256 end); - event Withdraw(uint256 loanId, address withdrawer); - LoanManager LM; - error InvalidWithdraw(); - error InvalidConduit(); - error ConduitTransferError(); - error InvalidStakeType(); - error LoanDoesNotExist(); - error RecallBeforeHoneymoonExpiry(); - error LoanHasNotBeenRefinanced(); - error WithdrawDoesNotExist(); - - ConsiderationInterface public constant seaport = - ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); - mapping(uint256 => Recall) public recalls; - struct Details { - // period at the begininng of a loan in which the loan cannot be recalled - uint256 honeymoon; - // period for which the recall is active - uint256 recallWindow; - // days of interest a recaller must stake - uint256 recallStakeDuration; - // maximum rate of the recall before failure - uint256 recallMax; - } - - struct Recall { - address payable recaller; - uint64 start; - } - - constructor(LoanManager LM_) { - LM = LM_; - } - - function getRecallRate( - LoanManager.Loan calldata loan - ) external view returns (uint256) { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - uint256 loanId = loan.getId(); - // calculates the porportion of time elapsed, then multiplies times the max rate - return - details.recallMax.mulWad( - (block.timestamp - recalls[loanId].start).divWad(details.recallWindow) - ); - } - - function recall(LoanManager.Loan memory loan, address conduit) external { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - - if ((loan.start + details.honeymoon) > block.timestamp) { - revert RecallBeforeHoneymoonExpiry(); + using FixedPointMathLib for uint256; + using {StarPortLib.getId} for LoanManager.Loan; + + event Recalled(uint256 loandId, address recaller, uint256 end); + event Withdraw(uint256 loanId, address withdrawer); + + LoanManager LM; + + error InvalidWithdraw(); + error InvalidConduit(); + error ConduitTransferError(); + error InvalidStakeType(); + error LoanDoesNotExist(); + error RecallBeforeHoneymoonExpiry(); + error LoanHasNotBeenRefinanced(); + error WithdrawDoesNotExist(); + + ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); + mapping(uint256 => Recall) public recalls; + + struct Details { + // period at the begininng of a loan in which the loan cannot be recalled + uint256 honeymoon; + // period for which the recall is active + uint256 recallWindow; + // days of interest a recaller must stake + uint256 recallStakeDuration; + // maximum rate of the recall before failure + uint256 recallMax; } - // get conduitController - (, , address conduitController) = seaport.information(); - // validate that the provided conduit is owned by the msg.sender - if ( - ConduitControllerInterface(conduitController).ownerOf(conduit) != - msg.sender - ) { - revert InvalidConduit(); + struct Recall { + address payable recaller; + uint64 start; } - ReceivedItem[] memory recallConsideration = _generateRecallConsideration( - loan, - 0, - details.recallStakeDuration, - 1e18, - payable(address(this)) - ); - if ( - ConduitInterface(conduit).execute( - _packageTransfers(recallConsideration, msg.sender) - ) != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); + + constructor(LoanManager LM_) { + LM = LM_; + } + + function getRecallRate(LoanManager.Loan calldata loan) external view returns (uint256) { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + uint256 loanId = loan.getId(); + // calculates the porportion of time elapsed, then multiplies times the max rate + return details.recallMax.mulWad((block.timestamp - recalls[loanId].start).divWad(details.recallWindow)); } - bytes memory encodedLoan = abi.encode(loan); + function recall(LoanManager.Loan memory loan, address conduit) external { + Details memory details = abi.decode(loan.terms.hookData, (Details)); - uint256 loanId = uint256(keccak256(encodedLoan)); + if ((loan.start + details.honeymoon) > block.timestamp) { + revert RecallBeforeHoneymoonExpiry(); + } - if (!LM.active(loanId)) revert LoanDoesNotExist(); + // get conduitController + (,, address conduitController) = seaport.information(); + // validate that the provided conduit is owned by the msg.sender + if (ConduitControllerInterface(conduitController).ownerOf(conduit) != msg.sender) { + revert InvalidConduit(); + } + ReceivedItem[] memory recallConsideration = + _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, payable(address(this))); + if ( + ConduitInterface(conduit).execute(_packageTransfers(recallConsideration, msg.sender)) + != ConduitInterface.execute.selector + ) { + revert ConduitTransferError(); + } - recalls[loanId] = Recall(payable(msg.sender), uint64(block.timestamp)); - emit Recalled(loanId, msg.sender, loan.start + details.recallWindow); - } + bytes memory encodedLoan = abi.encode(loan); - // transfers all stake to anyone who asks after the LM token is burned - function withdraw( - LoanManager.Loan memory loan, - address payable receiver - ) external { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - bytes memory encodedLoan = abi.encode(loan); - uint256 loanId = uint256(keccak256(encodedLoan)); + 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.active(loanId)) revert LoanDoesNotExist(); - Recall storage recall = recalls[loanId]; - // ensure that a recall exists for the provided tokenId, ensure that the recall - if (recall.start == 0 || recall.recaller == address(0)) { - revert WithdrawDoesNotExist(); + recalls[loanId] = Recall(payable(msg.sender), uint64(block.timestamp)); + emit Recalled(loanId, msg.sender, loan.start + details.recallWindow); } - ReceivedItem[] memory recallConsideration = _generateRecallConsideration( - loan, - 0, - details.recallStakeDuration, - 1e18, - receiver - ); - recall.recaller = payable(address(0)); - recall.start = 0; + // transfers all stake to anyone who asks after the LM token is burned + function withdraw(LoanManager.Loan memory loan, address payable receiver) external { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + bytes memory encodedLoan = abi.encode(loan); + 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(); + + Recall storage recall = recalls[loanId]; + // ensure that a recall exists for the provided tokenId, ensure that the recall + if (recall.start == 0 || recall.recaller == address(0)) { + revert WithdrawDoesNotExist(); + } - for (uint256 i; i < recallConsideration.length; ) { + ReceivedItem[] memory recallConsideration = + _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, receiver); + recall.recaller = payable(address(0)); + recall.start = 0; - if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType(); + for (uint256 i; i < recallConsideration.length;) { + if (loan.debt[i].itemType != ItemType.ERC20) revert InvalidStakeType(); - ERC20(loan.debt[i].token).transfer( - receiver, - recallConsideration[i].amount - ); + ERC20(loan.debt[i].token).transfer(receiver, recallConsideration[i].amount); - unchecked { - ++i; - } + unchecked { + ++i; + } + } + + emit Withdraw(loanId, receiver); + } + + function _getRecallStake(LoanManager.Loan memory loan, uint256 start, uint256 end) + internal + view + returns (uint256[] memory recallStake) + { + BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + recallStake = new uint256[](loan.debt.length); + for (uint256 i; i < loan.debt.length;) { + recallStake[i] = BasePricing(loan.terms.pricing).getInterest(loan, details, start, end, i); + + unchecked { + ++i; + } + } } - emit Withdraw(loanId, receiver); - } - - function _getRecallStake( - LoanManager.Loan memory loan, - uint256 start, - uint256 end - ) internal view returns (uint256[] memory recallStake) { - BasePricing.Details memory details = abi.decode( - loan.terms.pricingData, - (BasePricing.Details) - ); - recallStake = new uint256[](loan.debt.length); - for (uint256 i; i < loan.debt.length; ) { - recallStake[i] = BasePricing(loan.terms.pricing).getInterest( - loan, - details, - start, - end, - i - ); - - unchecked { - ++i; - } + function generateRecallConsideration(LoanManager.Loan memory loan, uint256 proportion, address payable receiver) + external + view + returns (ReceivedItem[] memory consideration) + { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + return _generateRecallConsideration(loan, 0, details.recallStakeDuration, 1e18, receiver); } - } - - function generateRecallConsideration( - LoanManager.Loan memory loan, - uint256 proportion, - address payable receiver - ) external view returns (ReceivedItem[] memory consideration) { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - return - _generateRecallConsideration( - loan, - 0, - details.recallStakeDuration, - 1e18, - receiver - ); - } - - function _generateRecallConsideration( - LoanManager.Loan memory loan, - uint256 start, - uint256 end, - uint256 proportion, - address payable receiver - ) internal view returns (ReceivedItem[] memory consideration) { - uint256[] memory stake = _getRecallStake(loan, start, end); - consideration = new ReceivedItem[](stake.length); - - for (uint256 i; i < consideration.length; ) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, - identifier: loan.debt[i].identifier, - amount: stake[i].mulWad(proportion), - token: loan.debt[i].token, - recipient: receiver - }); - unchecked { - ++i; - } + + function _generateRecallConsideration( + LoanManager.Loan memory loan, + uint256 start, + uint256 end, + uint256 proportion, + address payable receiver + ) internal view returns (ReceivedItem[] memory consideration) { + uint256[] memory stake = _getRecallStake(loan, start, end); + consideration = new ReceivedItem[](stake.length); + + for (uint256 i; i < consideration.length;) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: stake[i].mulWad(proportion), + token: loan.debt[i].token, + recipient: receiver + }); + unchecked { + ++i; + } + } } - } } diff --git a/src/hooks/FixedTermHook.sol b/src/hooks/FixedTermHook.sol index 5ffa3d85..7917647f 100644 --- a/src/hooks/FixedTermHook.sol +++ b/src/hooks/FixedTermHook.sol @@ -4,14 +4,12 @@ import {LoanManager} from "src/LoanManager.sol"; import {SettlementHook} from "src/hooks/SettlementHook.sol"; contract FixedTermHook is SettlementHook { - struct Details { - uint256 loanDuration; - } + struct Details { + uint256 loanDuration; + } - function isActive( - LoanManager.Loan calldata loan - ) external view override returns (bool) { - Details memory details = abi.decode(loan.terms.hookData, (Details)); - return loan.start + details.loanDuration > block.timestamp; - } + function isActive(LoanManager.Loan calldata loan) external view override returns (bool) { + Details memory details = abi.decode(loan.terms.hookData, (Details)); + return loan.start + details.loanDuration > block.timestamp; + } } diff --git a/src/hooks/SettlementHook.sol b/src/hooks/SettlementHook.sol index af762e0d..7f31fcd0 100644 --- a/src/hooks/SettlementHook.sol +++ b/src/hooks/SettlementHook.sol @@ -3,7 +3,5 @@ pragma solidity =0.8.17; import {LoanManager} from "src/LoanManager.sol"; abstract contract SettlementHook { - function isActive( - LoanManager.Loan calldata loan - ) external view virtual returns (bool); + function isActive(LoanManager.Loan calldata loan) external view virtual returns (bool); } diff --git a/src/lib/StarPortLib.sol b/src/lib/StarPortLib.sol index 50d4cad6..838d3278 100644 --- a/src/lib/StarPortLib.sol +++ b/src/lib/StarPortLib.sol @@ -1,99 +1,91 @@ pragma solidity ^0.8.17; -import { - ItemType, - ReceivedItem, - SpentItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {LoanManager} from "src/LoanManager.sol"; library StarPortLib { - function getId( - LoanManager.Loan memory loan - ) internal pure returns (uint256 loanId) { - loanId = uint256(keccak256(abi.encode(loan))); - } - - function toReceivedItems( - SpentItem[] calldata spentItems, - address recipient - ) internal pure returns (ReceivedItem[] memory result) { - assembly { - //set `result` pointer to free memory - result := mload(0x40) - - let n := spentItems.length - - //store length of `result` - mstore(result, n) - - //set `ptr` to start of first struct offset - let ptr := add(result, 0x20) - - //`s` = offset of first struct - let s := add(ptr, mul(n, 0x20)) - - //expand memory - mstore(0x40, add(ptr, mul(n, 0xC0))) - - //store struct offsets - first offset starts at end of offsets - let o := s - let c := spentItems.offset - let r := add(s, 0x80) // first recipient offset - for { - - } lt(ptr, s) { - ptr := add(ptr, 0x20) - c := add(c, 0x80) - o := add(o, 0xA0) - r := add(r, 0xA0) - } { - mstore(ptr, o) //store offset - calldatacopy(o, c, 0x80) - mstore(r, recipient) //set recipient - } + function getId(LoanManager.Loan memory loan) internal pure returns (uint256 loanId) { + loanId = uint256(keccak256(abi.encode(loan))); } - } - - function encodeWithRecipient( - ReceivedItem[] calldata receivedItems, - address recipient - ) internal pure returns (ReceivedItem[] memory result) { - assembly { - //set `result` pointer to free memory - result := mload(0x40) - - let n := receivedItems.length - - //store length of `result` - mstore(result, n) - - //set `ptr` to start of first struct offset - let ptr := add(result, 0x20) - - //`s` = offset of first struct - let s := add(ptr, mul(n, 0x20)) - - //expand memory - mstore(0x40, add(ptr, mul(n, 0xC0))) - - //copy struct data - calldatacopy(s, receivedItems.offset, mul(n, 0xA0)) - - //store struct offsets - first offset starts at end of offsets - let o := s - let r := add(s, 0x80) // first recipient offset - for { - - } lt(ptr, s) { - ptr := add(ptr, 0x20) - o := add(o, 0xA0) - r := add(r, 0xA0) - } { - mstore(ptr, o) //store offset - mstore(r, recipient) //set recipient - } + + function toReceivedItems(SpentItem[] calldata spentItems, address recipient) + internal + pure + returns (ReceivedItem[] memory result) + { + assembly { + //set `result` pointer to free memory + result := mload(0x40) + + let n := spentItems.length + + //store length of `result` + mstore(result, n) + + //set `ptr` to start of first struct offset + let ptr := add(result, 0x20) + + //`s` = offset of first struct + let s := add(ptr, mul(n, 0x20)) + + //expand memory + mstore(0x40, add(ptr, mul(n, 0xC0))) + + //store struct offsets - first offset starts at end of offsets + let o := s + let c := spentItems.offset + let r := add(s, 0x80) // first recipient offset + for {} lt(ptr, s) { + ptr := add(ptr, 0x20) + c := add(c, 0x80) + o := add(o, 0xA0) + r := add(r, 0xA0) + } { + mstore(ptr, o) //store offset + calldatacopy(o, c, 0x80) + mstore(r, recipient) //set recipient + } + } + } + + function encodeWithRecipient(ReceivedItem[] calldata receivedItems, address recipient) + internal + pure + returns (ReceivedItem[] memory result) + { + assembly { + //set `result` pointer to free memory + result := mload(0x40) + + let n := receivedItems.length + + //store length of `result` + mstore(result, n) + + //set `ptr` to start of first struct offset + let ptr := add(result, 0x20) + + //`s` = offset of first struct + let s := add(ptr, mul(n, 0x20)) + + //expand memory + mstore(0x40, add(ptr, mul(n, 0xC0))) + + //copy struct data + calldatacopy(s, receivedItems.offset, mul(n, 0xA0)) + + //store struct offsets - first offset starts at end of offsets + let o := s + let r := add(s, 0x80) // first recipient offset + for {} lt(ptr, s) { + ptr := add(ptr, 0x20) + o := add(o, 0xA0) + r := add(r, 0xA0) + } { + mstore(ptr, o) //store offset + mstore(r, recipient) //set recipient + } + } } - } } diff --git a/src/originators/MerkleOriginator.sol b/src/originators/MerkleOriginator.sol index ea1ea664..fd021054 100644 --- a/src/originators/MerkleOriginator.sol +++ b/src/originators/MerkleOriginator.sol @@ -7,123 +7,99 @@ import {MerkleProofLib} from "solady/src/utils/MerkleProofLib.sol"; import "forge-std/console.sol"; contract MerkleOriginator is Originator { - error InvalidMerkleProof(); - - constructor( - LoanManager LM_, - address strategist_, - uint256 fee_ - ) Originator(LM_, strategist_, fee_) {} - - struct MerkleProof { - bytes32 root; - bytes32[] proof; - } - - struct Details { - address custodian; - address conduit; - address issuer; - uint256 deadline; - LoanManager.Terms terms; - SpentItem[] collateral; - SpentItem[] debt; - bytes validator; - } - - function terms( - bytes calldata details - ) public view override returns (LoanManager.Terms memory) { - return abi.decode(details, (Details)).terms; - } - - function _build( - Request calldata params, - Details memory details - ) internal view returns (Response memory response) { - bool needsMint = details.issuer.code.length > 0; - response = Response({terms: details.terms, issuer: details.issuer}); - } - - function _validateMerkleProof( - MerkleProof memory incomingMerkleProof, - bytes32 leafHash - ) internal view virtual { - if ( - !MerkleProofLib.verify( - incomingMerkleProof.proof, - incomingMerkleProof.root, - leafHash - ) - ) { - revert InvalidMerkleProof(); + error InvalidMerkleProof(); + + constructor(LoanManager LM_, address strategist_, uint256 fee_) Originator(LM_, strategist_, fee_) {} + + struct MerkleProof { + bytes32 root; + bytes32[] proof; } - } - - function execute( - Request calldata params - ) external override returns (Response memory response) { - Details memory details = abi.decode(params.details, (Details)); - MerkleProof memory proof = abi.decode(details.validator, (MerkleProof)); - - bytes32 leafHash = keccak256( - abi.encode( - details.custodian, - details.conduit, - details.issuer, - details.deadline, - details.terms, - details.collateral, - details.debt - ) - ); - - _validateMerkleProof(proof, leafHash); - console.logBytes32(proof.root); - _validateSignature( - keccak256(encodeWithAccountCounter(strategist, proof.root)), - params.signature - ); - - if (block.timestamp > details.deadline) { - revert InvalidDeadline(); + + struct Details { + address custodian; + address conduit; + address issuer; + uint256 deadline; + LoanManager.Terms terms; + SpentItem[] collateral; + SpentItem[] debt; + bytes validator; + } + + function terms(bytes calldata details) public view override returns (LoanManager.Terms memory) { + return abi.decode(details, (Details)).terms; + } + + function _build(Request calldata params, Details memory details) internal view returns (Response memory response) { + bool needsMint = details.issuer.code.length > 0; + response = Response({terms: details.terms, issuer: details.issuer}); + } + + function _validateMerkleProof(MerkleProof memory incomingMerkleProof, bytes32 leafHash) internal view virtual { + if (!MerkleProofLib.verify(incomingMerkleProof.proof, incomingMerkleProof.root, leafHash)) { + revert InvalidMerkleProof(); + } } - _validateAsk(params, details); - if (params.debt.length > 1) { - revert InvalidDebtLength(); + function execute(Request calldata params) external override returns (Response memory response) { + Details memory details = abi.decode(params.details, (Details)); + MerkleProof memory proof = abi.decode(details.validator, (MerkleProof)); + + bytes32 leafHash = keccak256( + abi.encode( + details.custodian, + details.conduit, + details.issuer, + details.deadline, + details.terms, + details.collateral, + details.debt + ) + ); + + _validateMerkleProof(proof, leafHash); + console.logBytes32(proof.root); + _validateSignature(keccak256(encodeWithAccountCounter(strategist, proof.root)), params.signature); + + if (block.timestamp > details.deadline) { + revert InvalidDeadline(); + } + + _validateAsk(params, details); + if (params.debt.length > 1) { + revert InvalidDebtLength(); + } + + if ( + ConduitInterface(details.conduit).execute(_packageTransfers(params.debt, params.receiver, details.issuer)) + != ConduitInterface.execute.selector + ) { + revert ConduitTransferError(); + } + + response = _build(params, details); } - if ( - ConduitInterface(details.conduit).execute( - _packageTransfers(params.debt, params.receiver, details.issuer) - ) != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); + function _validateAsk(Request calldata request, Details memory details) internal { + // if (request.borrower == address(0)) { + // revert InvalidBorrower(); + // } + if (request.custodian != details.custodian) { + revert InvalidCustodian(); + } + // if (request.details.length > 0) { + // revert InvalidDetails(); + // } + // if (keccak256(request.collateral)) } - response = _build(params, details); - } - - function _validateAsk( - Request calldata request, - Details memory details - ) internal { - // if (request.borrower == address(0)) { - // revert InvalidBorrower(); - // } - if (request.custodian != details.custodian) { - revert InvalidCustodian(); + function getFeeConsideration(LoanManager.Loan calldata loan) + external + view + override + returns (ReceivedItem[] memory consideration) + { + consideration; } - // if (request.details.length > 0) { - // revert InvalidDetails(); - // } - // if (keccak256(request.collateral)) - } - - function getFeeConsideration( - LoanManager.Loan calldata loan - ) external view override returns (ReceivedItem[] memory consideration) { - consideration; - } } diff --git a/src/originators/Originator.sol b/src/originators/Originator.sol index f9fb8a66..ff5eaada 100644 --- a/src/originators/Originator.sol +++ b/src/originators/Originator.sol @@ -2,201 +2,166 @@ pragma solidity =0.8.17; import {LoanManager} from "src/LoanManager.sol"; -import { - ItemType, - ReceivedItem, - SpentItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import { - ConduitTransfer, - ConduitItemType -} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; -import { - ConduitControllerInterface -} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; -import { - ConduitInterface -} from "seaport-types/src/interfaces/ConduitInterface.sol"; +import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {ConduitControllerInterface} from "seaport-types/src/interfaces/ConduitControllerInterface.sol"; +import {ConduitInterface} from "seaport-types/src/interfaces/ConduitInterface.sol"; import {ECDSA} from "solady/src/utils/ECDSA.sol"; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; // Validator abstract contract that lays out the necessary structure and functions for the validator abstract contract Originator { - enum State { - INITIALIZED, - CLOSED - } - struct Response { - LoanManager.Terms terms; - address issuer; - } - - struct Request { - address custodian; - address receiver; - SpentItem[] collateral; - SpentItem[] debt; - bytes details; - bytes signature; - } - - event Origination( - uint256 indexed loanId, - address indexed issuer, - bytes nlrDetails - ); - - event CounterUpdated(); - error InvalidCaller(); - error InvalidCustodian(); - error InvalidDeadline(); - error InvalidOriginator(); - error InvalidCollateral(); - error InvalidBorrowAmount(); - error InvalidAmount(); - error InvalidDebtToken(); - error InvalidRate(); - error InvalidSigner(); - error InvalidLoan(); - error InvalidTerms(); - error InvalidDebtLength(); - error InvalidDebtAmount(); - error ConduitTransferError(); - - LoanManager public immutable LM; - - // Define the EIP712 domain and typehash constants for generating signatures - bytes32 constant EIP_DOMAIN = - keccak256( - "EIP712Domain(string version,uint256 chainId,address verifyingContract)" - ); - bytes32 public constant ORIGINATOR_DETAILS_TYPEHASH = - keccak256( - "OriginatorDetails(address originator,uint256 nonce,bytes32 hash)" - ); - bytes32 constant VERSION = keccak256("0"); - - bytes32 internal immutable _DOMAIN_SEPARATOR; - - // Strategist address and fee - address public strategist; - uint256 public strategistFee; - uint256 private _counter; - - constructor(LoanManager LM_, address strategist_, uint256 fee_) { - strategist = strategist_; - strategistFee = fee_; - LM = LM_; - _DOMAIN_SEPARATOR = keccak256( - abi.encode( - EIP_DOMAIN, - VERSION, //version - block.chainid, - address(this) - ) - ); - } - - function _packageTransfers( - SpentItem[] memory loan, - address borrower, - address issuer - ) internal pure returns (ConduitTransfer[] memory transfers) { - uint256 i = 0; - transfers = new ConduitTransfer[](loan.length); - for (; i < loan.length; ) { - ConduitItemType itemType; - SpentItem memory debt = loan[i]; - - assembly { - itemType := mload(debt) - switch itemType - case 1 { + enum State { + INITIALIZED, + CLOSED + } - } - case 2 { + struct Response { + LoanManager.Terms terms; + address issuer; + } + + struct Request { + address custodian; + address receiver; + SpentItem[] collateral; + SpentItem[] debt; + bytes details; + bytes signature; + } + event Origination(uint256 indexed loanId, address indexed issuer, bytes nlrDetails); + + event CounterUpdated(); + + error InvalidCaller(); + error InvalidCustodian(); + error InvalidDeadline(); + error InvalidOriginator(); + error InvalidCollateral(); + error InvalidBorrowAmount(); + error InvalidAmount(); + error InvalidDebtToken(); + error InvalidRate(); + error InvalidSigner(); + error InvalidLoan(); + error InvalidTerms(); + error InvalidDebtLength(); + error InvalidDebtAmount(); + error ConduitTransferError(); + + LoanManager public immutable LM; + + // Define the EIP712 domain and typehash constants for generating signatures + bytes32 constant EIP_DOMAIN = keccak256("EIP712Domain(string version,uint256 chainId,address verifyingContract)"); + bytes32 public constant ORIGINATOR_DETAILS_TYPEHASH = + keccak256("OriginatorDetails(address originator,uint256 nonce,bytes32 hash)"); + bytes32 constant VERSION = keccak256("0"); + + bytes32 internal immutable _DOMAIN_SEPARATOR; + + // Strategist address and fee + address public strategist; + uint256 public strategistFee; + uint256 private _counter; + + constructor(LoanManager LM_, address strategist_, uint256 fee_) { + strategist = strategist_; + strategistFee = fee_; + LM = LM_; + _DOMAIN_SEPARATOR = keccak256( + abi.encode( + EIP_DOMAIN, + VERSION, //version + block.chainid, + address(this) + ) + ); + } + + function _packageTransfers(SpentItem[] memory loan, address borrower, address issuer) + internal + pure + returns (ConduitTransfer[] memory transfers) + { + uint256 i = 0; + transfers = new ConduitTransfer[](loan.length); + for (; i < loan.length;) { + ConduitItemType itemType; + SpentItem memory debt = loan[i]; + + assembly { + itemType := mload(debt) + switch itemType + case 1 {} + case 2 {} + case 3 {} + default { revert(0, 0) } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) + } + transfers[i] = ConduitTransfer({ + itemType: itemType, + from: issuer, + token: loan[i].token, + identifier: loan[i].identifier, + amount: loan[i].amount, + to: borrower + }); + unchecked { + ++i; + } } - case 3 { + } + + function terms(bytes calldata) external view virtual returns (LoanManager.Terms memory); + + // Abstract function to execute the loan, to be overridden in child contracts + function execute(Request calldata) external virtual returns (Response memory); + // Encode the data with the account's nonce for generating a signature + function encodeWithAccountCounter(address account, bytes32 contextHash) + public + view + virtual + returns (bytes memory) + { + bytes32 hash = keccak256(abi.encode(ORIGINATOR_DETAILS_TYPEHASH, address(this), _counter, contextHash)); + + return abi.encodePacked(bytes1(0x19), bytes1(0x01), _DOMAIN_SEPARATOR, hash); + } + + function getStrategistData() public view virtual returns (address, uint256) { + return (strategist, strategistFee); + } + + // Get the nonce of an account + function getCounter() public view virtual returns (uint256) { + return _counter; + } + + // Function to increment the nonce of the sender + function incrementCounter() external { + if (msg.sender != strategist) { + revert InvalidCaller(); } - default { - revert(0, 0) - } //TODO: Update with error selector - InvalidContext(ContextErrors.INVALID_LOAN) - } - transfers[i] = ConduitTransfer({ - itemType: itemType, - from: issuer, - token: loan[i].token, - identifier: loan[i].identifier, - amount: loan[i].amount, - to: borrower - }); - unchecked { - ++i; - } + _counter += uint256(blockhash(block.number - 1) << 0x80); + emit CounterUpdated(); } - } - - function terms( - bytes calldata - ) external view virtual returns (LoanManager.Terms memory); - - // Abstract function to execute the loan, to be overridden in child contracts - function execute(Request calldata) external virtual returns (Response memory); - - // Encode the data with the account's nonce for generating a signature - function encodeWithAccountCounter( - address account, - bytes32 contextHash - ) public view virtual returns (bytes memory) { - bytes32 hash = keccak256( - abi.encode( - ORIGINATOR_DETAILS_TYPEHASH, - address(this), - _counter, - contextHash - ) - ); - - return - abi.encodePacked(bytes1(0x19), bytes1(0x01), _DOMAIN_SEPARATOR, hash); - } - - function getStrategistData() public view virtual returns (address, uint256) { - return (strategist, strategistFee); - } - - // Get the nonce of an account - function getCounter() public view virtual returns (uint256) { - return _counter; - } - - // Function to increment the nonce of the sender - function incrementCounter() external { - if (msg.sender != strategist) { - revert InvalidCaller(); + + // Function to generate the domain separator for signatures + function domainSeparator() public view virtual returns (bytes32) { + return _DOMAIN_SEPARATOR; } - _counter += uint256(blockhash(block.number - 1) << 0x80); - emit CounterUpdated(); - } - - // Function to generate the domain separator for signatures - function domainSeparator() public view virtual returns (bytes32) { - return _DOMAIN_SEPARATOR; - } - - function _validateSignature( - bytes32 hash, - bytes calldata signature - ) internal view virtual { - if (!SignatureCheckerLib.isValidSignatureNow(strategist, hash, signature)) { - revert InvalidSigner(); + + function _validateSignature(bytes32 hash, bytes calldata signature) internal view virtual { + if (!SignatureCheckerLib.isValidSignatureNow(strategist, hash, signature)) { + revert InvalidSigner(); + } } - } - function getFeeConsideration( - LoanManager.Loan calldata loan - ) external view virtual returns (ReceivedItem[] memory consideration); + function getFeeConsideration(LoanManager.Loan calldata loan) + external + view + virtual + returns (ReceivedItem[] memory consideration); } diff --git a/src/originators/UniqueOriginator.sol b/src/originators/UniqueOriginator.sol index c86332b0..8a87aa33 100644 --- a/src/originators/UniqueOriginator.sol +++ b/src/originators/UniqueOriginator.sol @@ -5,86 +5,71 @@ import {LoanManager} from "src/LoanManager.sol"; import "src/originators/Originator.sol"; contract UniqueOriginator is Originator { - constructor( - LoanManager LM_, - address strategist_, - uint256 fee_ - ) Originator(LM_, strategist_, fee_) {} + constructor(LoanManager LM_, address strategist_, uint256 fee_) Originator(LM_, strategist_, fee_) {} - struct Details { - address custodian; - address conduit; - address issuer; - uint256 deadline; - LoanManager.Terms terms; - SpentItem[] collateral; - SpentItem[] debt; - } + struct Details { + address custodian; + address conduit; + address issuer; + uint256 deadline; + LoanManager.Terms terms; + SpentItem[] collateral; + SpentItem[] debt; + } - function terms( - bytes calldata details - ) public view override returns (LoanManager.Terms memory) { - return abi.decode(details, (Details)).terms; - } + function terms(bytes calldata details) public view override returns (LoanManager.Terms memory) { + return abi.decode(details, (Details)).terms; + } - function _build( - Request calldata params, - Details memory details - ) internal view returns (Response memory response) { - bool needsMint = details.issuer.code.length > 0; - response = Response({terms: details.terms, issuer: details.issuer}); - } + function _build(Request calldata params, Details memory details) internal view returns (Response memory response) { + bool needsMint = details.issuer.code.length > 0; + response = Response({terms: details.terms, issuer: details.issuer}); + } - function execute( - Request calldata params - ) external override returns (Response memory response) { - bytes32 contextHash = keccak256(params.details); + function execute(Request calldata params) external override returns (Response memory response) { + bytes32 contextHash = keccak256(params.details); - _validateSignature( - keccak256(encodeWithAccountCounter(strategist, contextHash)), - params.signature - ); - Details memory details = abi.decode(params.details, (Details)); + _validateSignature(keccak256(encodeWithAccountCounter(strategist, contextHash)), params.signature); + Details memory details = abi.decode(params.details, (Details)); - if (block.timestamp > details.deadline) { - revert InvalidDeadline(); - } + if (block.timestamp > details.deadline) { + revert InvalidDeadline(); + } - _validateAsk(params, details); - if (params.debt.length > 1) { - revert InvalidDebtLength(); - } + _validateAsk(params, details); + if (params.debt.length > 1) { + revert InvalidDebtLength(); + } - if ( - ConduitInterface(details.conduit).execute( - _packageTransfers(params.debt, params.receiver, details.issuer) - ) != ConduitInterface.execute.selector - ) { - revert ConduitTransferError(); - } + if ( + ConduitInterface(details.conduit).execute(_packageTransfers(params.debt, params.receiver, details.issuer)) + != ConduitInterface.execute.selector + ) { + revert ConduitTransferError(); + } - response = _build(params, details); - } + response = _build(params, details); + } - function _validateAsk( - Request calldata request, - Details memory details - ) internal { - // if (request.borrower == address(0)) { - // revert InvalidBorrower(); - // } - if (request.custodian != details.custodian) { - revert InvalidCustodian(); + function _validateAsk(Request calldata request, Details memory details) internal { + // if (request.borrower == address(0)) { + // revert InvalidBorrower(); + // } + if (request.custodian != details.custodian) { + revert InvalidCustodian(); + } + // if (request.details.length > 0) { + // revert InvalidDetails(); + // } + // if (keccak256(request.collateral)) } - // if (request.details.length > 0) { - // revert InvalidDetails(); - // } - // if (keccak256(request.collateral)) - } - function getFeeConsideration( - LoanManager.Loan calldata loan - ) external view override returns (ReceivedItem[] memory consideration) { - consideration; - } + function getFeeConsideration(LoanManager.Loan calldata loan) + external + view + override + returns (ReceivedItem[] memory consideration) + { + consideration; + } } diff --git a/src/pricing/AstariaV1Pricing.sol b/src/pricing/AstariaV1Pricing.sol index 22b202f0..69a679a5 100644 --- a/src/pricing/AstariaV1Pricing.sol +++ b/src/pricing/AstariaV1Pricing.sol @@ -1,4 +1,5 @@ pragma solidity =0.8.17; + import {LoanManager} from "src/LoanManager.sol"; import {CompoundInterestPricing} from "src/pricing/CompoundInterestPricing.sol"; import {Pricing} from "src/pricing/Pricing.sol"; @@ -11,65 +12,57 @@ import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; contract AstariaV1Pricing is CompoundInterestPricing { - using FixedPointMathLib for uint256; - using {StarPortLib.getId} for LoanManager.Loan; + using FixedPointMathLib for uint256; + using {StarPortLib.getId} for LoanManager.Loan; - constructor(LoanManager LM_) Pricing(LM_) {} + constructor(LoanManager LM_) Pricing(LM_) {} - error InsufficientRefinance(); + error InsufficientRefinance(); - function isValidRefinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address caller - ) - external - view - virtual - override - returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory recallConsideration - ) - { - // borrowers can refinance a loan at any time - if (caller != loan.borrower) { - // check if a recall is occuring - AstariaV1SettlementHook hook = AstariaV1SettlementHook(loan.terms.hook); - Details memory newDetails = abi.decode(newPricingData, (Details)); - 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(); - } - // recall is not occuring - else revert InvalidRefinance(); - Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); + function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + external + view + virtual + override + returns ( + ReceivedItem[] memory repayConsideration, + ReceivedItem[] memory carryConsideration, + ReceivedItem[] memory recallConsideration + ) + { + // borrowers can refinance a loan at any time + if (caller != loan.borrower) { + // check if a recall is occuring + AstariaV1SettlementHook hook = AstariaV1SettlementHook(loan.terms.hook); + Details memory newDetails = abi.decode(newPricingData, (Details)); + 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(); + } + // recall is not occuring + else { + revert InvalidRefinance(); + } + Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); - uint256 proportion; - address payable receiver = payable(loan.issuer); - uint256 loanId = loan.getId(); - // scenario where the recaller is not penalized - // recaller stake is refunded - if (newDetails.rate > oldDetails.rate) { - proportion = 1e18; - (receiver, ) = hook.recalls(loanId); - } else { - // scenario where the recaller is penalized - // essentially the old lender and the new lender split the stake of the recaller - // split is proportional to the difference in rate - proportion = - 1e18 - - (oldDetails.rate - newDetails.rate).divWad(oldDetails.rate); - } - recallConsideration = hook.generateRecallConsideration( - loan, - proportion, - receiver - ); - } + uint256 proportion; + address payable receiver = payable(loan.issuer); + uint256 loanId = loan.getId(); + // scenario where the recaller is not penalized + // recaller stake is refunded + if (newDetails.rate > oldDetails.rate) { + proportion = 1e18; + (receiver,) = hook.recalls(loanId); + } else { + // scenario where the recaller is penalized + // essentially the old lender and the new lender split the stake of the recaller + // split is proportional to the difference in rate + proportion = 1e18 - (oldDetails.rate - newDetails.rate).divWad(oldDetails.rate); + } + recallConsideration = hook.generateRecallConsideration(loan, proportion, receiver); + } - (repayConsideration, carryConsideration) = getPaymentConsideration(loan); - } + (repayConsideration, carryConsideration) = getPaymentConsideration(loan); + } } diff --git a/src/pricing/BasePricing.sol b/src/pricing/BasePricing.sol index 02b6598f..f744393e 100644 --- a/src/pricing/BasePricing.sol +++ b/src/pricing/BasePricing.sol @@ -11,134 +11,123 @@ import {BaseHook} from "src/hooks/BaseHook.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; abstract contract BasePricing is Pricing { - using FixedPointMathLib for uint256; - using {StarPortLib.getId} for LoanManager.Loan; - struct Details { - uint256 rate; - uint256 carryRate; - } - - function getPaymentConsideration( - LoanManager.Loan memory loan - ) - public - view - virtual - override - returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration - ) - { - repayConsideration = _generateRepayConsideration(loan); - carryConsideration = _generateRepayCarryConsideration(loan); - } - - function getOwed( - LoanManager.Loan memory loan - ) public view returns (uint256[] memory) { - Details memory details = abi.decode(loan.terms.pricingData, (Details)); - return _getOwed(loan, details, loan.start, block.timestamp); - } - - function _getOwedCarry( - LoanManager.Loan memory loan, - Details memory details, - uint256 timestamp - ) internal view returns (uint256[] memory carryOwed) { - carryOwed = new uint256[](loan.debt.length); - uint256 i = 0; - - for (; i < loan.debt.length; ) { - uint256 carry = getInterest(loan, details, loan.start, timestamp, i) - .mulWad(details.carryRate); - carryOwed[i] = carry; - unchecked { - ++i; - } + using FixedPointMathLib for uint256; + using {StarPortLib.getId} for LoanManager.Loan; + + struct Details { + uint256 rate; + uint256 carryRate; } - } - - function _getOwed( - LoanManager.Loan memory loan, - Details memory details, - uint256 start, - uint256 end - ) internal view returns (uint256[] memory updatedDebt) { - updatedDebt = new uint256[](loan.debt.length); - for (uint256 i = 0; i < loan.debt.length; i++) { - updatedDebt[i] = - loan.debt[i].amount + - getInterest(loan, details, start, end, i); + + function getPaymentConsideration(LoanManager.Loan memory loan) + public + view + virtual + override + returns (ReceivedItem[] memory repayConsideration, ReceivedItem[] memory carryConsideration) + { + repayConsideration = _generateRepayConsideration(loan); + carryConsideration = _generateRepayCarryConsideration(loan); } - } - - function getInterest( - LoanManager.Loan memory loan, - Details memory details, - uint256 start, - uint256 end, - uint256 index - ) public view returns (uint256) { - uint256 delta_t = end - start; - return calculateInterest(delta_t, details.rate, loan.debt[index].amount); - } - - function calculateInterest( - uint256 delta_t, - uint256 amount, - uint256 rate // expressed as SPR seconds per rate - ) public pure virtual returns (uint256); - - function _generateRepayConsideration( - LoanManager.Loan memory loan - ) internal view returns (ReceivedItem[] memory consideration) { - Details memory details = abi.decode(loan.terms.pricingData, (Details)); - - consideration = new ReceivedItem[](loan.debt.length); - uint256[] memory owing = _getOwed( - loan, - details, - loan.start, - block.timestamp - ); - bool isActive = LM.active(loan.getId()); - - uint256 i = 0; - for (; i < consideration.length; ) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, - identifier: loan.debt[i].identifier, - amount: owing[i], - token: loan.debt[i].token, - recipient: payable(loan.issuer) - }); - unchecked { - ++i; - } + + function getOwed(LoanManager.Loan memory loan) public view returns (uint256[] memory) { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + return _getOwed(loan, details, loan.start, block.timestamp); } - } - - function _generateRepayCarryConsideration( - LoanManager.Loan memory loan - ) internal view returns (ReceivedItem[] memory consideration) { - Details memory details = abi.decode(loan.terms.pricingData, (Details)); - - if (details.carryRate == 0) return new ReceivedItem[](0); - uint256[] memory owing = _getOwedCarry(loan, details, block.timestamp); - consideration = new ReceivedItem[](owing.length); - uint256 i = 0; - for (; i < consideration.length; ) { - consideration[i] = ReceivedItem({ - itemType: loan.debt[i].itemType, - identifier: loan.debt[i].identifier, - amount: owing[i], - token: loan.debt[i].token, - recipient: payable(loan.originator) - }); - unchecked { - ++i; - } + + function _getOwedCarry(LoanManager.Loan memory loan, Details memory details, uint256 timestamp) + internal + view + returns (uint256[] memory carryOwed) + { + carryOwed = new uint256[](loan.debt.length); + uint256 i = 0; + + for (; i < loan.debt.length;) { + uint256 carry = getInterest(loan, details, loan.start, timestamp, i).mulWad(details.carryRate); + carryOwed[i] = carry; + unchecked { + ++i; + } + } + } + + function _getOwed(LoanManager.Loan memory loan, Details memory details, uint256 start, uint256 end) + internal + view + returns (uint256[] memory updatedDebt) + { + updatedDebt = new uint256[](loan.debt.length); + for (uint256 i = 0; i < loan.debt.length; i++) { + updatedDebt[i] = loan.debt[i].amount + getInterest(loan, details, start, end, i); + } + } + + function getInterest( + LoanManager.Loan memory loan, + Details memory details, + uint256 start, + uint256 end, + uint256 index + ) public view returns (uint256) { + uint256 delta_t = end - start; + return calculateInterest(delta_t, details.rate, loan.debt[index].amount); + } + + function calculateInterest( + uint256 delta_t, + uint256 amount, + uint256 rate // expressed as SPR seconds per rate + ) public pure virtual returns (uint256); + + function _generateRepayConsideration(LoanManager.Loan memory loan) + internal + view + returns (ReceivedItem[] memory consideration) + { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + + consideration = new ReceivedItem[](loan.debt.length); + uint256[] memory owing = _getOwed(loan, details, loan.start, block.timestamp); + bool isActive = LM.active(loan.getId()); + + uint256 i = 0; + for (; i < consideration.length;) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: owing[i], + token: loan.debt[i].token, + recipient: payable(loan.issuer) + }); + unchecked { + ++i; + } + } + } + + function _generateRepayCarryConsideration(LoanManager.Loan memory loan) + internal + view + returns (ReceivedItem[] memory consideration) + { + Details memory details = abi.decode(loan.terms.pricingData, (Details)); + + if (details.carryRate == 0) return new ReceivedItem[](0); + uint256[] memory owing = _getOwedCarry(loan, details, block.timestamp); + consideration = new ReceivedItem[](owing.length); + uint256 i = 0; + for (; i < consideration.length;) { + consideration[i] = ReceivedItem({ + itemType: loan.debt[i].itemType, + identifier: loan.debt[i].identifier, + amount: owing[i], + token: loan.debt[i].token, + recipient: payable(loan.originator) + }); + unchecked { + ++i; + } + } } - } } diff --git a/src/pricing/BaseRecallPricing.sol b/src/pricing/BaseRecallPricing.sol index 1b1cfaac..ba7e2ab8 100644 --- a/src/pricing/BaseRecallPricing.sol +++ b/src/pricing/BaseRecallPricing.sol @@ -11,32 +11,25 @@ import {BaseHook} from "src/hooks/BaseHook.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; abstract contract BaseRecallPricing is BasePricing { - function isValidRefinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address caller - ) - external - view - virtual - override - returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory recallConsideration - ) - { - Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); - Details memory newDetails = abi.decode(newPricingData, (Details)); - bool isRecalled = BaseHook(loan.terms.hook).isRecalled(loan); + function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + external + view + virtual + override + returns ( + ReceivedItem[] memory repayConsideration, + ReceivedItem[] memory carryConsideration, + ReceivedItem[] memory recallConsideration + ) + { + Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); + Details memory newDetails = abi.decode(newPricingData, (Details)); + bool isRecalled = BaseHook(loan.terms.hook).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 ReceivedItem[](0); + //todo: figure out the proper flow for here + if ((isRecalled && newDetails.rate >= oldDetails.rate) || (newDetails.rate < oldDetails.rate)) { + (repayConsideration, carryConsideration) = getPaymentConsideration(loan); + recallConsideration = new ReceivedItem[](0); + } } - } } diff --git a/src/pricing/CompoundInterestPricing.sol b/src/pricing/CompoundInterestPricing.sol index f25b6f55..84711230 100644 --- a/src/pricing/CompoundInterestPricing.sol +++ b/src/pricing/CompoundInterestPricing.sol @@ -6,20 +6,20 @@ import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; import {BaseRecallPricing} from "src/pricing/BaseRecallPricing.sol"; abstract contract CompoundInterestPricing is BaseRecallPricing { - using FixedPointMathLib for uint256; + using FixedPointMathLib for uint256; - // function getInterest( - // uint256 delta_t, - // uint256 amount, - // uint256 rate // expressed as SPR seconds per rate - // ) public pure override returns (uint256) { - // return amount.mulWad((2718281828459045235 ** rate.mulWad(delta_t)) / 1e18); - // } - function calculateInterest( - uint256 delta_t, - uint256 amount, - uint256 rate // expressed as SPR seconds per rate - ) public pure override returns (uint256) { - return (delta_t * rate).mulWad(amount); - } + // function getInterest( + // uint256 delta_t, + // uint256 amount, + // uint256 rate // expressed as SPR seconds per rate + // ) public pure override returns (uint256) { + // return amount.mulWad((2718281828459045235 ** rate.mulWad(delta_t)) / 1e18); + // } + function calculateInterest( + uint256 delta_t, + uint256 amount, + uint256 rate // expressed as SPR seconds per rate + ) public pure override returns (uint256) { + return (delta_t * rate).mulWad(amount); + } } diff --git a/src/pricing/Pricing.sol b/src/pricing/Pricing.sol index d65bfb79..049b53f4 100644 --- a/src/pricing/Pricing.sol +++ b/src/pricing/Pricing.sol @@ -6,28 +6,23 @@ import "forge-std/console.sol"; import "seaport/lib/seaport-sol/src/lib/ReceivedItemLib.sol"; abstract contract Pricing { - LoanManager LM; - error InvalidRefinance(); + LoanManager LM; - constructor(LoanManager LM_) { - LM = LM_; - } + error InvalidRefinance(); - function getPaymentConsideration( - LoanManager.Loan memory loan - ) public view virtual returns (ReceivedItem[] memory, ReceivedItem[] memory); + constructor(LoanManager LM_) { + LM = LM_; + } - function isValidRefinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address caller - ) - external - view - virtual - returns ( - ReceivedItem[] memory, - ReceivedItem[] memory, - ReceivedItem[] memory - ); + function getPaymentConsideration(LoanManager.Loan memory loan) + public + view + virtual + returns (ReceivedItem[] memory, ReceivedItem[] memory); + + function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + external + view + virtual + returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory); } diff --git a/src/pricing/SimpleInterestPricing.sol b/src/pricing/SimpleInterestPricing.sol index 366154b7..4d3f035c 100644 --- a/src/pricing/SimpleInterestPricing.sol +++ b/src/pricing/SimpleInterestPricing.sol @@ -6,42 +6,38 @@ import {LoanManager} from "src/LoanManager.sol"; import {Pricing} from "src/pricing/Pricing.sol"; contract SimpleInterestPricing is BasePricing { - using FixedPointMathLib for uint256; + using FixedPointMathLib for uint256; - constructor(LoanManager LM_) Pricing(LM_) {} + constructor(LoanManager LM_) Pricing(LM_) {} - function calculateInterest( - uint256 delta_t, - uint256 amount, - uint256 rate // expressed as SPR seconds per rate - ) public pure override returns (uint256) { - return (delta_t * rate).mulWad(amount); - } + function calculateInterest( + uint256 delta_t, + uint256 amount, + uint256 rate // expressed as SPR seconds per rate + ) public pure override returns (uint256) { + return (delta_t * rate).mulWad(amount); + } - function isValidRefinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address caller - ) - external - view - virtual - override - returns ( - ReceivedItem[] memory repayConsideration, - ReceivedItem[] memory carryConsideration, - ReceivedItem[] memory additionalConsideration - ) - { - Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); - Details memory newDetails = abi.decode(newPricingData, (Details)); + function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + external + view + virtual + override + returns ( + ReceivedItem[] memory repayConsideration, + ReceivedItem[] memory carryConsideration, + ReceivedItem[] memory additionalConsideration + ) + { + Details memory oldDetails = abi.decode(loan.terms.pricingData, (Details)); + Details memory newDetails = abi.decode(newPricingData, (Details)); - //todo: figure out the proper flow for here - if ((newDetails.rate < oldDetails.rate)) { - (repayConsideration, carryConsideration) = getPaymentConsideration(loan); - additionalConsideration = new ReceivedItem[](0); - } else { - revert InvalidRefinance(); + //todo: figure out the proper flow for here + if ((newDetails.rate < oldDetails.rate)) { + (repayConsideration, carryConsideration) = getPaymentConsideration(loan); + additionalConsideration = new ReceivedItem[](0); + } else { + revert InvalidRefinance(); + } } - } } diff --git a/src/scripts/Deploy.sol b/src/scripts/Deploy.sol index 410a8597..3c6771c2 100644 --- a/src/scripts/Deploy.sol +++ b/src/scripts/Deploy.sol @@ -8,18 +8,18 @@ import "../handlers/EnglishAuctionHandler.sol"; import "../hooks/FixedTermHook.sol"; contract Deploy is Script { - function run() public { - vm.startBroadcast(); - LoanManager LM = new LoanManager(); - UniqueOriginator UO = new UniqueOriginator(LM, msg.sender, 0); - Pricing PR = new SimpleInterestPricing(LM); - address EAZone = address(0x004C00500000aD104D7DBd00e3ae0A5C00560C00); - SettlementHandler SH = new EnglishAuctionHandler( + function run() public { + vm.startBroadcast(); + LoanManager LM = new LoanManager(); + UniqueOriginator UO = new UniqueOriginator(LM, msg.sender, 0); + Pricing PR = new SimpleInterestPricing(LM); + address EAZone = address(0x004C00500000aD104D7DBd00e3ae0A5C00560C00); + SettlementHandler SH = new EnglishAuctionHandler( LM, ConsiderationInterface(LM.seaport()), EAZone ); - FixedTermHook HK = new FixedTermHook(); - vm.stopBroadcast(); - } + FixedTermHook HK = new FixedTermHook(); + vm.stopBroadcast(); + } } diff --git a/test/AstariaV1Test.sol b/test/AstariaV1Test.sol index 657820ec..e27683d5 100644 --- a/test/AstariaV1Test.sol +++ b/test/AstariaV1Test.sol @@ -1,4 +1,5 @@ pragma solidity =0.8.17; + import "forge-std/console2.sol"; import "./StarPortTest.sol"; @@ -12,56 +13,46 @@ import {BaseRecall} from "src/hooks/BaseRecall.sol"; import {AstariaV1SettlementHandler} from "src/handlers/AstariaV1SettlementHandler.sol"; // import "forge-std/console2.sol"; + contract AstariaV1Test is StarPortTest { - Account recaller; - address recallerConduit; - bytes32 conduitKeyRecaller; - function setUp() public override { - super.setUp(); - - recaller = makeAndAllocateAccount("recaller"); - - // erc20s[1].mint(recaller.addr, 10000); - - pricing = new AstariaV1Pricing(LM); - handler = new AstariaV1SettlementHandler(LM); - hook = new AstariaV1SettlementHook(LM); - - conduitKeyRecaller = bytes32( - uint256(uint160(address(recaller.addr))) << 96 - ); - - - vm.startPrank(recaller.addr); - recallerConduit = conduitController.createConduit( - conduitKeyRecaller, - recaller.addr - ); - conduitController.updateChannel(recallerConduit, address(hook), true); - erc20s[0].approve(address(recallerConduit), 100000); - vm.stopPrank(); - - // // 1% interest rate per second - defaultPricingData = - abi.encode( - BasePricing.Details({ - carryRate: (uint256(1e16) * 10), - rate: (uint256(1e16) * 150) / (365 * 1 days) - }) - ); - - defaultHandlerData = new bytes(0); - - defaultHookData = - abi.encode( - BaseRecall.Details({ - honeymoon: 1 days, - recallWindow: 3 days, - recallStakeDuration: 30 days, - // 1000% APR - recallMax: (uint256(1e16) * 1000) / (365 * 1 days) - }) - ); - } - -} \ No newline at end of file + Account recaller; + address recallerConduit; + bytes32 conduitKeyRecaller; + + function setUp() public override { + super.setUp(); + + recaller = makeAndAllocateAccount("recaller"); + + // erc20s[1].mint(recaller.addr, 10000); + + pricing = new AstariaV1Pricing(LM); + handler = new AstariaV1SettlementHandler(LM); + hook = new AstariaV1SettlementHook(LM); + + conduitKeyRecaller = bytes32(uint256(uint160(address(recaller.addr))) << 96); + + vm.startPrank(recaller.addr); + recallerConduit = conduitController.createConduit(conduitKeyRecaller, recaller.addr); + conduitController.updateChannel(recallerConduit, address(hook), true); + erc20s[0].approve(address(recallerConduit), 100000); + vm.stopPrank(); + + // // 1% interest rate per second + defaultPricingData = abi.encode( + BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: (uint256(1e16) * 150) / (365 * 1 days)}) + ); + + defaultHandlerData = new bytes(0); + + defaultHookData = abi.encode( + BaseRecall.Details({ + honeymoon: 1 days, + recallWindow: 3 days, + recallStakeDuration: 30 days, + // 1000% APR + recallMax: (uint256(1e16) * 1000) / (365 * 1 days) + }) + ); + } +} diff --git a/test/StarPortTest.sol b/test/StarPortTest.sol index 55bc52fb..61d555c9 100644 --- a/test/StarPortTest.sol +++ b/test/StarPortTest.sol @@ -7,34 +7,25 @@ import {LoanManager} from "src/LoanManager.sol"; import {Pricing} from "src/pricing/Pricing.sol"; import {Originator} from "src/originators/Originator.sol"; import { - ItemType, - ReceivedItem, - OfferItem, - SpentItem, - OrderParameters + ItemType, + ReceivedItem, + OfferItem, + SpentItem, + OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {OrderParametersLib} from "seaport/lib/seaport-sol/src/lib/OrderParametersLib.sol"; +import {ConduitTransfer, ConduitItemType} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; +import {ConsiderationInterface} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; import { - OrderParametersLib -} from "seaport/lib/seaport-sol/src/lib/OrderParametersLib.sol"; -import { - ConduitTransfer, - ConduitItemType -} from "seaport-types/src/conduit/lib/ConduitStructs.sol"; -import { - ConsiderationInterface -} from "seaport-types/src/interfaces/ConsiderationInterface.sol"; -import { - ConsiderationItem, - AdvancedOrder, - CriteriaResolver, - Fulfillment, - FulfillmentComponent, - OrderType + ConsiderationItem, + AdvancedOrder, + CriteriaResolver, + Fulfillment, + FulfillmentComponent, + OrderType } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Conduit} from "seaport-core/src/conduit/Conduit.sol"; -import { - ConduitController -} from "seaport-core/src/conduit/ConduitController.sol"; +import {ConduitController} from "seaport-core/src/conduit/ConduitController.sol"; import {Consideration} from "seaport-core/src/lib/Consideration.sol"; //import { // ReferenceConsideration as Consideration @@ -55,9 +46,7 @@ import {Merkle} from "seaport/lib/murky/src/Merkle.sol"; import {BaseOrderTest} from "seaport/test/foundry/utils/BaseOrderTest.sol"; import {TestERC721} from "seaport/contracts/test/TestERC721.sol"; import {TestERC20} from "seaport/contracts/test/TestERC20.sol"; -import { - ConsiderationItemLib -} from "seaport/lib/seaport-sol/src/lib/ConsiderationItemLib.sol"; +import {ConsiderationItemLib} from "seaport/lib/seaport-sol/src/lib/ConsiderationItemLib.sol"; import {Custodian} from "src/Custodian.sol"; import "../src/custodians/AAVEPoolCustodian.sol"; import "seaport/lib/seaport-sol/src/lib/AdvancedOrderLib.sol"; @@ -65,788 +54,675 @@ import "seaport/lib/seaport-sol/src/lib/AdvancedOrderLib.sol"; import {TermEnforcer} from "src/enforcers/TermEnforcer.sol"; interface IWETH9 { - function deposit() external payable; + function deposit() external payable; - function withdraw(uint256) external; + function withdraw(uint256) external; } contract StarPortTest is BaseOrderTest { - SettlementHook fixedTermHook; - SettlementHook astariaSettlementHook; + SettlementHook fixedTermHook; + SettlementHook astariaSettlementHook; - SettlementHandler dutchAuctionHandler; - SettlementHandler englishAuctionHandler; - SettlementHandler astariaSettlementHandler; + SettlementHandler dutchAuctionHandler; + SettlementHandler englishAuctionHandler; + SettlementHandler astariaSettlementHandler; - Pricing simpleInterestPricing; - Pricing astariaPricing; + Pricing simpleInterestPricing; + Pricing astariaPricing; - ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); + ConsiderationInterface public constant seaport = ConsiderationInterface(0x2e234DAe75C793f67A35089C9d99245E1C58470b); - Pricing pricing; - SettlementHandler handler; - SettlementHook hook; + Pricing pricing; + SettlementHandler handler; + SettlementHook hook; - uint256 defaultLoanDuration = 14 days; + uint256 defaultLoanDuration = 14 days; - // 1% interest rate per second - bytes defaultPricingData = - abi.encode( - BasePricing.Details({ - carryRate: (uint256(1e16) * 10), - rate: (uint256(1e16) * 150) / (365 * 1 days) - }) - ); + // 1% interest rate per second + bytes defaultPricingData = + abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: (uint256(1e16) * 150) / (365 * 1 days)})); - bytes defaultHandlerData = - abi.encode( - DutchAuctionHandler.Details({ - startingPrice: uint256(500 ether), - endingPrice: 100 wei, - window: 7 days - }) - ); - bytes defaultHookData = - abi.encode(FixedTermHook.Details({loanDuration: defaultLoanDuration})); - - Account borrower; - Account lender; - Account seller; - Account strategist; - Account refinancer; - - bytes32 conduitKey; - address lenderConduit; - address refinancerConduit; - address seaportAddr; - LoanManager LM; - Custodian custodian; - UniqueOriginator UO; - MerkleOriginator MO; - - CapitalPool CP; - - bytes32 conduitKeyRefinancer; - - function _deployAndConfigureConsideration() public { - conduitController = new ConduitController(); - - consideration = new Consideration(address(conduitController)); - } - - function setUp() public virtual override { - _deployAndConfigureConsideration(); - vm.label(alice, "alice"); - vm.label(bob, "bob"); - vm.label(cal, "cal"); - vm.label(address(this), "testContract"); - - _deployTestTokenContracts(); - - erc20s = [token1, token2, token3]; - erc721s = [test721_1, test721_2, test721_3]; - erc1155s = [test1155_1, test1155_2, test1155_3]; - vm.label(address(erc20s[0]), "debtToken"); - vm.label(address(erc721s[0]), "721 collateral 1"); - vm.label(address(erc721s[1]), "721 collateral 2"); - vm.label(address(erc1155s[0]), "1155 collateral 1"); - vm.label(address(erc1155s[1]), "1155 collateral 2"); - - // allocate funds and tokens to test addresses - allocateTokensAndApprovals(address(this), uint128(MAX_INT)); - - borrower = makeAndAllocateAccount("borrower"); - lender = makeAndAllocateAccount("lender"); - strategist = makeAndAllocateAccount("strategist"); - seller = makeAndAllocateAccount("seller"); - refinancer = makeAndAllocateAccount("refinancer"); - - LM = new LoanManager(); - custodian = new Custodian(LM, address(consideration)); - UO = new UniqueOriginator(LM, strategist.addr, 1e16); - MO = new MerkleOriginator(LM, strategist.addr, 1e16); - CP = new CapitalPool(address(erc20s[0]), conduitController, address(MO)); - pricing = new SimpleInterestPricing(LM); - handler = new DutchAuctionHandler(LM); - hook = new FixedTermHook(); - vm.label(address(erc721s[0]), "Collateral NFT"); - vm.label(address(erc721s[1]), "Collateral2 NFT"); - vm.label(address(erc20s[0]), "Debt Token"); - vm.label(address(erc20s[1]), "Collateral Token"); - { - vm.startPrank(borrower.addr); - erc721s[1].mint(seller.addr, 1); - erc721s[0].mint(borrower.addr, 1); - erc721s[0].mint(borrower.addr, 2); - erc721s[0].mint(borrower.addr, 3); - erc20s[1].mint(borrower.addr, 10000); - vm.stopPrank(); - } - conduitKeyOne = bytes32(uint256(uint160(address(lender.addr))) << 96); - conduitKeyRefinancer = bytes32( - uint256(uint160(address(refinancer.addr))) << 96 + bytes defaultHandlerData = abi.encode( + DutchAuctionHandler.Details({startingPrice: uint256(500 ether), endingPrice: 100 wei, window: 7 days}) ); + bytes defaultHookData = abi.encode(FixedTermHook.Details({loanDuration: defaultLoanDuration})); - vm.startPrank(lender.addr); - erc20s[0].approve(address(CP), 10 ether); - CP.deposit(10 ether, lender.addr); - lenderConduit = conduitController.createConduit(conduitKeyOne, lender.addr); - - conduitController.updateChannel(lenderConduit, address(UO), true); - conduitController.updateChannel(lenderConduit, address(MO), true); - erc20s[0].approve(address(lenderConduit), 100000); - vm.stopPrank(); - vm.startPrank(refinancer.addr); - refinancerConduit = conduitController.createConduit( - conduitKeyRefinancer, - refinancer.addr - ); - // console.log("Refinancer", refinancer.addr); - conduitController.updateChannel(refinancerConduit, address(LM), true); - erc20s[0].approve(address(refinancerConduit), 100000); - vm.stopPrank(); + Account borrower; + Account lender; + Account seller; + Account strategist; + Account refinancer; + + bytes32 conduitKey; + address lenderConduit; + address refinancerConduit; + address seaportAddr; + LoanManager LM; + Custodian custodian; + UniqueOriginator UO; + MerkleOriginator MO; - ///////// + CapitalPool CP; - fixedTermHook = new FixedTermHook(); - astariaSettlementHook = new AstariaV1SettlementHook(LM); + bytes32 conduitKeyRefinancer; + + function _deployAndConfigureConsideration() public { + conduitController = new ConduitController(); + + consideration = new Consideration(address(conduitController)); + } - dutchAuctionHandler = new DutchAuctionHandler(LM); - englishAuctionHandler = new EnglishAuctionHandler({ + function setUp() public virtual override { + _deployAndConfigureConsideration(); + vm.label(alice, "alice"); + vm.label(bob, "bob"); + vm.label(cal, "cal"); + vm.label(address(this), "testContract"); + + _deployTestTokenContracts(); + + erc20s = [token1, token2, token3]; + erc721s = [test721_1, test721_2, test721_3]; + erc1155s = [test1155_1, test1155_2, test1155_3]; + vm.label(address(erc20s[0]), "debtToken"); + vm.label(address(erc721s[0]), "721 collateral 1"); + vm.label(address(erc721s[1]), "721 collateral 2"); + vm.label(address(erc1155s[0]), "1155 collateral 1"); + vm.label(address(erc1155s[1]), "1155 collateral 2"); + + // allocate funds and tokens to test addresses + allocateTokensAndApprovals(address(this), uint128(MAX_INT)); + + borrower = makeAndAllocateAccount("borrower"); + lender = makeAndAllocateAccount("lender"); + strategist = makeAndAllocateAccount("strategist"); + seller = makeAndAllocateAccount("seller"); + refinancer = makeAndAllocateAccount("refinancer"); + + LM = new LoanManager(); + custodian = new Custodian(LM, address(consideration)); + UO = new UniqueOriginator(LM, strategist.addr, 1e16); + MO = new MerkleOriginator(LM, strategist.addr, 1e16); + CP = new CapitalPool(address(erc20s[0]), conduitController, address(MO)); + pricing = new SimpleInterestPricing(LM); + handler = new DutchAuctionHandler(LM); + hook = new FixedTermHook(); + vm.label(address(erc721s[0]), "Collateral NFT"); + vm.label(address(erc721s[1]), "Collateral2 NFT"); + vm.label(address(erc20s[0]), "Debt Token"); + vm.label(address(erc20s[1]), "Collateral Token"); + { + vm.startPrank(borrower.addr); + erc721s[1].mint(seller.addr, 1); + erc721s[0].mint(borrower.addr, 1); + erc721s[0].mint(borrower.addr, 2); + erc721s[0].mint(borrower.addr, 3); + erc20s[1].mint(borrower.addr, 10000); + vm.stopPrank(); + } + conduitKeyOne = bytes32(uint256(uint160(address(lender.addr))) << 96); + conduitKeyRefinancer = bytes32(uint256(uint160(address(refinancer.addr))) << 96); + + vm.startPrank(lender.addr); + erc20s[0].approve(address(CP), 10 ether); + CP.deposit(10 ether, lender.addr); + lenderConduit = conduitController.createConduit(conduitKeyOne, lender.addr); + + conduitController.updateChannel(lenderConduit, address(UO), true); + conduitController.updateChannel(lenderConduit, address(MO), true); + erc20s[0].approve(address(lenderConduit), 100000); + vm.stopPrank(); + vm.startPrank(refinancer.addr); + refinancerConduit = conduitController.createConduit(conduitKeyRefinancer, refinancer.addr); + // console.log("Refinancer", refinancer.addr); + conduitController.updateChannel(refinancerConduit, address(LM), true); + erc20s[0].approve(address(refinancerConduit), 100000); + vm.stopPrank(); + + ///////// + + fixedTermHook = new FixedTermHook(); + astariaSettlementHook = new AstariaV1SettlementHook(LM); + + dutchAuctionHandler = new DutchAuctionHandler(LM); + englishAuctionHandler = new EnglishAuctionHandler({ LM_: LM, consideration_: seaport, EAZone_: 0x110b2B128A9eD1be5Ef3232D8e4E41640dF5c2Cd }); - astariaSettlementHandler = new AstariaV1SettlementHandler(LM); - - simpleInterestPricing = new SimpleInterestPricing(LM); - astariaPricing = new AstariaV1Pricing(LM); - } - - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) public pure override returns (bytes4) { - return this.onERC721Received.selector; - } - - ConsiderationItem[] selectedCollateral; - ConsiderationItem[] collateral20; - SpentItem[] debt; - - struct NewLoanData { - address custodian; - LoanManager.Caveat[] caveats; - bytes details; - } - - function newLoan( - NewLoanData memory loanData, - Originator originator, - ConsiderationItem[] storage collateral - ) internal returns (LoanManager.Loan memory) { - bool isTrusted = loanData.caveats.length == 0; - { - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - strategist.key, - keccak256( - originator.encodeWithAccountCounter( - strategist.addr, - keccak256(loanData.details) - ) - ) - ); - - LoanManager.Loan memory loan = LoanManager.Loan({ - custodian: address(loanData.custodian), - issuer: address(0), - borrower: borrower.addr, - originator: isTrusted ? address(originator) : address(0), - terms: originator.terms(loanData.details), - debt: debt, - collateral: ConsiderationItemLib.toSpentItemArray(collateral), - start: uint256(0) - }); - return - _executeNLR( - LoanManager.Obligation({ - custodian: address(loanData.custodian), - borrower: borrower.addr, - debt: debt, - salt: bytes32(0), - details: loanData.details, - signature: abi.encodePacked(r, s, v), - caveats: loanData.caveats, - originator: address(originator) - }), - collateral // for building contract offer - ); + astariaSettlementHandler = new AstariaV1SettlementHandler(LM); + + simpleInterestPricing = new SimpleInterestPricing(LM); + astariaPricing = new AstariaV1Pricing(LM); } - } - - function newLoanWithMerkleProof( - NewLoanData memory loanData, - Originator originator, - ConsiderationItem[] storage collateral - ) internal returns (LoanManager.Loan memory) { - bool isTrusted = loanData.caveats.length == 0; + + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + public + pure + override + returns (bytes4) { - MerkleOriginator.Details memory details = abi.decode( - loanData.details, - (MerkleOriginator.Details) - ); - MerkleOriginator.MerkleProof memory merkleData = abi.decode( - details.validator, - (MerkleOriginator.MerkleProof) - ); - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - strategist.key, - keccak256( - originator.encodeWithAccountCounter(strategist.addr, merkleData.root) - ) - ); - - LoanManager.Loan memory loan = LoanManager.Loan({ - custodian: address(loanData.custodian), - issuer: address(0), - borrower: borrower.addr, - originator: isTrusted ? address(originator) : address(0), - terms: originator.terms(loanData.details), - debt: debt, - collateral: ConsiderationItemLib.toSpentItemArray(collateral), - start: uint256(0) - }); - return - _executeNLR( - LoanManager.Obligation({ - custodian: address(loanData.custodian), - borrower: borrower.addr, - debt: debt, - salt: bytes32(0), - details: loanData.details, - signature: abi.encodePacked(r, s, v), - caveats: loanData.caveats, - originator: address(originator) - }), - collateral // for building contract offer - ); + return this.onERC721Received.selector; } - } - - function buyNowPayLater( - AdvancedOrder memory thingToBuy, - NewLoanData memory loanData, - Originator originator, - ConsiderationItem[] storage collateral - ) internal { - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - strategist.key, - keccak256( - originator.encodeWithAccountCounter( - strategist.addr, - keccak256(loanData.details) - ) - ) - ); - LoanManager.Loan memory loan = LoanManager.Loan({ - custodian: address(loanData.custodian), - issuer: address(0), - borrower: borrower.addr, - originator: address(0), - terms: originator.terms(loanData.details), - debt: debt, - collateral: ConsiderationItemLib.toSpentItemArray(collateral), - start: uint256(0) - }); + ConsiderationItem[] selectedCollateral; + ConsiderationItem[] collateral20; + SpentItem[] debt; - _buyNowPLNLR( - thingToBuy, - LoanManager.Obligation({ - custodian: address(loanData.custodian), - borrower: borrower.addr, - debt: debt, - details: loanData.details, - salt: bytes32(0), - signature: abi.encodePacked(r, s, v), - caveats: loanData.caveats, - originator: address(originator) - }), - collateral // for building contract offer - ); - } - - function _buildContractOrder( - address offerer, - OfferItem[] memory offer, - ConsiderationItem[] memory consider - ) internal view returns (OrderParameters memory op) { - op = OrderParameters({ - offerer: offerer, - zone: address(0), - offer: offer, - consideration: consider, - orderType: OrderType.CONTRACT, - startTime: block.timestamp, - endTime: block.timestamp + 100, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: consider.length - }); - } - - function _executeRepayLoan(LoanManager.Loan memory activeLoan) internal { - ( - ReceivedItem[] memory loanPayment, - ReceivedItem[] memory carryPayment - ) = Pricing(activeLoan.terms.pricing).getPaymentConsideration(activeLoan); - uint256 i = 0; - ConsiderationItem[] memory consider = new ConsiderationItem[]( - loanPayment.length + carryPayment.length - ); - for (; i < loanPayment.length; ) { - consider[i].token = loanPayment[i].token; - consider[i].itemType = loanPayment[i].itemType; - consider[i].identifierOrCriteria = loanPayment[i].identifier; - consider[i].startAmount = 5 ether; - //TODO: update this - consider[i].endAmount = 5 ether; - consider[i].recipient = loanPayment[i].recipient; - unchecked { - ++i; - } - } - for (; i < carryPayment.length; ) { - consider[i].token = carryPayment[i].token; - consider[i].itemType = carryPayment[i].itemType; - consider[i].identifierOrCriteria = carryPayment[i].identifier; - consider[i].startAmount = carryPayment[i].amount; - //TODO: update this - consider[i].endAmount = carryPayment[i].amount; - consider[i].recipient = carryPayment[i].recipient; - unchecked { - ++i; - } + struct NewLoanData { + address custodian; + LoanManager.Caveat[] caveats; + bytes details; } - OfferItem[] memory repayOffering = new OfferItem[]( - activeLoan.collateral.length - ); - i = 0; - for (; i < activeLoan.collateral.length; ) { - repayOffering[i] = OfferItem({ - itemType: activeLoan.collateral[i].itemType, - token: address(activeLoan.collateral[i].token), - identifierOrCriteria: activeLoan.collateral[i].identifier, - endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 - ? activeLoan.collateral[i].amount - : 1, - startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 - ? activeLoan.collateral[i].amount - : 1 - }); - unchecked { - ++i; - } + function newLoan(NewLoanData memory loanData, Originator originator, ConsiderationItem[] storage collateral) + internal + returns (LoanManager.Loan memory) + { + bool isTrusted = loanData.caveats.length == 0; + { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + strategist.key, + keccak256(originator.encodeWithAccountCounter(strategist.addr, keccak256(loanData.details))) + ); + + LoanManager.Loan memory loan = LoanManager.Loan({ + custodian: address(loanData.custodian), + issuer: address(0), + borrower: borrower.addr, + originator: isTrusted ? address(originator) : address(0), + terms: originator.terms(loanData.details), + debt: debt, + collateral: ConsiderationItemLib.toSpentItemArray(collateral), + start: uint256(0) + }); + return _executeNLR( + LoanManager.Obligation({ + custodian: address(loanData.custodian), + borrower: borrower.addr, + debt: debt, + salt: bytes32(0), + details: loanData.details, + signature: abi.encodePacked(r, s, v), + caveats: loanData.caveats, + originator: address(originator) + }), + collateral // for building contract offer + ); + } } - OrderParameters memory op = _buildContractOrder( - address(custodian), - repayOffering, - consider - ); - - AdvancedOrder memory x = AdvancedOrder({ - parameters: op, - numerator: 1, - denominator: 1, - signature: "0x", - extraData: abi.encode(activeLoan) - }); - uint256 balanceBefore = erc20s[0].balanceOf(borrower.addr); - vm.recordLogs(); - vm.startPrank(borrower.addr); - consideration.fulfillAdvancedOrder({ - advancedOrder: x, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(this) - }); - // Vm.Log[] memory logs = vm.getRecordedLogs(); - - uint256 balanceAfter = erc20s[0].balanceOf(borrower.addr); - - vm.stopPrank(); - } - - function _buyNowPLNLR( - AdvancedOrder memory x, - // LoanManager.Loan memory loanAsk, - LoanManager.Obligation memory nlr, - ConsiderationItem[] memory collateral // collateral (nft) and weth (purchase price is incoming weth plus debt) - ) internal returns (LoanManager.Loan memory loan) { - //use murky to create a tree that is good - - bytes32 caveatHash = keccak256( - LM.encodeWithSaltAndBorrowerCounter( - nlr.borrower, - nlr.salt, - keccak256(abi.encode(nlr.caveats)) - ) - ); - OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); - - for (uint256 i; i < debt.length; ) { - offer[i] = OfferItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifierOrCriteria: debt[i].identifier, - startAmount: debt[i].amount, - endAmount: debt[i].amount - }); - unchecked { - ++i; - } + function newLoanWithMerkleProof( + NewLoanData memory loanData, + Originator originator, + ConsiderationItem[] storage collateral + ) internal returns (LoanManager.Loan memory) { + bool isTrusted = loanData.caveats.length == 0; + { + MerkleOriginator.Details memory details = abi.decode(loanData.details, (MerkleOriginator.Details)); + MerkleOriginator.MerkleProof memory merkleData = + abi.decode(details.validator, (MerkleOriginator.MerkleProof)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + strategist.key, keccak256(originator.encodeWithAccountCounter(strategist.addr, merkleData.root)) + ); + + LoanManager.Loan memory loan = LoanManager.Loan({ + custodian: address(loanData.custodian), + issuer: address(0), + borrower: borrower.addr, + originator: isTrusted ? address(originator) : address(0), + terms: originator.terms(loanData.details), + debt: debt, + collateral: ConsiderationItemLib.toSpentItemArray(collateral), + start: uint256(0) + }); + return _executeNLR( + LoanManager.Obligation({ + custodian: address(loanData.custodian), + borrower: borrower.addr, + debt: debt, + salt: bytes32(0), + details: loanData.details, + signature: abi.encodePacked(r, s, v), + caveats: loanData.caveats, + originator: address(originator) + }), + collateral // for building contract offer + ); + } } - offer[nlr.debt.length] = OfferItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1 - }); - - OfferItem[] memory zOffer = new OfferItem[](1); - zOffer[0] = OfferItem({ - itemType: nlr.debt[0].itemType, - token: nlr.debt[0].token, - identifierOrCriteria: nlr.debt[0].identifier, - startAmount: x.parameters.consideration[0].startAmount - - nlr.debt[0].amount, - endAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount - }); - ConsiderationItem[] memory zConsider = new ConsiderationItem[](1); - zConsider[0] = ConsiderationItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1, - recipient: payable(address(nlr.borrower)) - }); - OrderParameters memory zOP = OrderParameters({ - offerer: address(nlr.borrower), - zone: address(0), - offer: zOffer, - consideration: zConsider, - orderType: OrderType.FULL_OPEN, - startTime: block.timestamp, - endTime: block.timestamp + 100, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 1 - }); - AdvancedOrder memory z = AdvancedOrder({ - parameters: zOP, - numerator: 1, - denominator: 1, - signature: "", - extraData: "" - }); - - AdvancedOrder[] memory orders = new AdvancedOrder[](3); - orders[0] = x; - orders[1] = AdvancedOrder({ - parameters: _buildContractOrder(address(LM), offer, collateral), - numerator: 1, - denominator: 1, - signature: "", - extraData: abi.encode(nlr) - }); - orders[2] = z; - - // x is offering erc721 1 to satisfy y consideration - Fulfillment[] memory fill = new Fulfillment[](4); - fill[0] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[0].offerComponents[0] = FulfillmentComponent({ - orderIndex: 1, - itemIndex: 0 - }); - fill[0].considerationComponents[0] = FulfillmentComponent({ - orderIndex: 0, - itemIndex: 0 - }); - fill[1] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[1].offerComponents[0] = FulfillmentComponent({ - orderIndex: 2, - itemIndex: 0 - }); - - fill[1].considerationComponents[0] = FulfillmentComponent({ - orderIndex: 0, - itemIndex: 0 - }); - - fill[2] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[2].offerComponents[0] = FulfillmentComponent({ - orderIndex: 0, - itemIndex: 0 - }); - - fill[2].considerationComponents[0] = FulfillmentComponent({ - orderIndex: 1, - itemIndex: 0 - }); - - fill[3] = Fulfillment({ - offerComponents: new FulfillmentComponent[](1), - considerationComponents: new FulfillmentComponent[](1) - }); - - fill[3].offerComponents[0] = FulfillmentComponent({ - orderIndex: 1, - itemIndex: 1 - }); - - fill[3].considerationComponents[0] = FulfillmentComponent({ - orderIndex: 2, - itemIndex: 0 - }); - - uint256 balanceBefore = erc20s[0].balanceOf(seller.addr); - vm.recordLogs(); - vm.startPrank(borrower.addr); + function buyNowPayLater( + AdvancedOrder memory thingToBuy, + NewLoanData memory loanData, + Originator originator, + ConsiderationItem[] storage collateral + ) internal { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + strategist.key, keccak256(originator.encodeWithAccountCounter(strategist.addr, keccak256(loanData.details))) + ); - consideration.matchAdvancedOrders( - orders, - new CriteriaResolver[](0), - fill, - address(borrower.addr) - ); + LoanManager.Loan memory loan = LoanManager.Loan({ + custodian: address(loanData.custodian), + issuer: address(0), + borrower: borrower.addr, + originator: address(0), + terms: originator.terms(loanData.details), + debt: debt, + collateral: ConsiderationItemLib.toSpentItemArray(collateral), + start: uint256(0) + }); + + _buyNowPLNLR( + thingToBuy, + LoanManager.Obligation({ + custodian: address(loanData.custodian), + borrower: borrower.addr, + debt: debt, + details: loanData.details, + salt: bytes32(0), + signature: abi.encodePacked(r, s, v), + caveats: loanData.caveats, + originator: address(originator) + }), + collateral // for building contract offer + ); + } - (, loan) = abi.decode( - vm.getRecordedLogs()[debt.length + 1].data, - (uint256, LoanManager.Loan) - ); + function _buildContractOrder(address offerer, OfferItem[] memory offer, ConsiderationItem[] memory consider) + internal + view + returns (OrderParameters memory op) + { + op = OrderParameters({ + offerer: offerer, + zone: address(0), + offer: offer, + consideration: consider, + orderType: OrderType.CONTRACT, + startTime: block.timestamp, + endTime: block.timestamp + 100, + zoneHash: bytes32(0), + salt: 0, + conduitKey: bytes32(0), + totalOriginalConsiderationItems: consider.length + }); + } - assertEq(erc721s[1].ownerOf(1), address(nlr.custodian)); - assertEq( - erc20s[0].balanceOf(seller.addr), - balanceBefore + x.parameters.consideration[0].startAmount + function _executeRepayLoan(LoanManager.Loan memory activeLoan) internal { + (ReceivedItem[] memory loanPayment, ReceivedItem[] memory carryPayment) = + Pricing(activeLoan.terms.pricing).getPaymentConsideration(activeLoan); + uint256 i = 0; + ConsiderationItem[] memory consider = new ConsiderationItem[]( + loanPayment.length + carryPayment.length ); - vm.stopPrank(); - } - - function _executeNLR( - LoanManager.Obligation memory nlr, - ConsiderationItem[] memory collateral - ) internal returns (LoanManager.Loan memory loan) { - bytes32 caveatHash = keccak256( - LM.encodeWithSaltAndBorrowerCounter( - nlr.borrower, - nlr.salt, - keccak256(abi.encode(nlr.caveats)) - ) + for (; i < loanPayment.length;) { + consider[i].token = loanPayment[i].token; + consider[i].itemType = loanPayment[i].itemType; + consider[i].identifierOrCriteria = loanPayment[i].identifier; + consider[i].startAmount = 5 ether; + //TODO: update this + consider[i].endAmount = 5 ether; + consider[i].recipient = loanPayment[i].recipient; + unchecked { + ++i; + } + } + for (; i < carryPayment.length;) { + consider[i].token = carryPayment[i].token; + consider[i].itemType = carryPayment[i].itemType; + consider[i].identifierOrCriteria = carryPayment[i].identifier; + consider[i].startAmount = carryPayment[i].amount; + //TODO: update this + consider[i].endAmount = carryPayment[i].amount; + consider[i].recipient = carryPayment[i].recipient; + unchecked { + ++i; + } + } + + OfferItem[] memory repayOffering = new OfferItem[]( + activeLoan.collateral.length ); - OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); - - for (uint i; i < debt.length; ) { - offer[i] = OfferItem({ - itemType: debt[i].itemType, - token: debt[i].token, - identifierOrCriteria: debt[i].identifier, - startAmount: debt[i].amount, - endAmount: debt[i].amount - }); - unchecked { - ++i; - } + i = 0; + for (; i < activeLoan.collateral.length;) { + repayOffering[i] = OfferItem({ + itemType: activeLoan.collateral[i].itemType, + token: address(activeLoan.collateral[i].token), + identifierOrCriteria: activeLoan.collateral[i].identifier, + endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1, + startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1 + }); + unchecked { + ++i; + } + } + OrderParameters memory op = _buildContractOrder(address(custodian), repayOffering, consider); + + AdvancedOrder memory x = AdvancedOrder({ + parameters: op, + numerator: 1, + denominator: 1, + signature: "0x", + extraData: abi.encode(activeLoan) + }); + + uint256 balanceBefore = erc20s[0].balanceOf(borrower.addr); + vm.recordLogs(); + vm.startPrank(borrower.addr); + consideration.fulfillAdvancedOrder({ + advancedOrder: x, + criteriaResolvers: new CriteriaResolver[](0), + fulfillerConduitKey: bytes32(0), + recipient: address(this) + }); + // Vm.Log[] memory logs = vm.getRecordedLogs(); + + uint256 balanceAfter = erc20s[0].balanceOf(borrower.addr); + + vm.stopPrank(); } - offer[nlr.debt.length] = OfferItem({ - itemType: ItemType.ERC721, - token: address(LM), - identifierOrCriteria: uint256(caveatHash), - startAmount: 1, - endAmount: 1 - }); - - OrderParameters memory op = _buildContractOrder( - address(LM), - nlr.caveats.length == 0 ? new OfferItem[](0) : offer, - collateral - ); - - AdvancedOrder memory x = AdvancedOrder({ - parameters: op, - numerator: 1, - denominator: 1, - signature: "0x", - extraData: abi.encode(nlr) - }); + function _buyNowPLNLR( + AdvancedOrder memory x, + // LoanManager.Loan memory loanAsk, + LoanManager.Obligation memory nlr, + ConsiderationItem[] memory collateral // collateral (nft) and weth (purchase price is incoming weth plus debt) + ) internal returns (LoanManager.Loan memory loan) { + //use murky to create a tree that is good + + bytes32 caveatHash = + keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); + OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); + + for (uint256 i; i < debt.length;) { + offer[i] = OfferItem({ + itemType: debt[i].itemType, + token: debt[i].token, + identifierOrCriteria: debt[i].identifier, + startAmount: debt[i].amount, + endAmount: debt[i].amount + }); + unchecked { + ++i; + } + } + + offer[nlr.debt.length] = OfferItem({ + itemType: ItemType.ERC721, + token: address(LM), + identifierOrCriteria: uint256(caveatHash), + startAmount: 1, + endAmount: 1 + }); + + OfferItem[] memory zOffer = new OfferItem[](1); + zOffer[0] = OfferItem({ + itemType: nlr.debt[0].itemType, + token: nlr.debt[0].token, + identifierOrCriteria: nlr.debt[0].identifier, + startAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount, + endAmount: x.parameters.consideration[0].startAmount - nlr.debt[0].amount + }); + ConsiderationItem[] memory zConsider = new ConsiderationItem[](1); + zConsider[0] = ConsiderationItem({ + itemType: ItemType.ERC721, + token: address(LM), + identifierOrCriteria: uint256(caveatHash), + startAmount: 1, + endAmount: 1, + recipient: payable(address(nlr.borrower)) + }); + OrderParameters memory zOP = OrderParameters({ + offerer: address(nlr.borrower), + zone: address(0), + offer: zOffer, + consideration: zConsider, + orderType: OrderType.FULL_OPEN, + startTime: block.timestamp, + endTime: block.timestamp + 100, + zoneHash: bytes32(0), + salt: 0, + conduitKey: bytes32(0), + totalOriginalConsiderationItems: 1 + }); + AdvancedOrder memory z = + AdvancedOrder({parameters: zOP, numerator: 1, denominator: 1, signature: "", extraData: ""}); + + AdvancedOrder[] memory orders = new AdvancedOrder[](3); + orders[0] = x; + orders[1] = AdvancedOrder({ + parameters: _buildContractOrder(address(LM), offer, collateral), + numerator: 1, + denominator: 1, + signature: "", + extraData: abi.encode(nlr) + }); + orders[2] = z; + + // x is offering erc721 1 to satisfy y consideration + Fulfillment[] memory fill = new Fulfillment[](4); + fill[0] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[0].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + fill[0].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + fill[1] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[1].offerComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); + + fill[1].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + + fill[2] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[2].offerComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + + fill[2].considerationComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + + fill[3] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + fill[3].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 1}); + + fill[3].considerationComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); + + uint256 balanceBefore = erc20s[0].balanceOf(seller.addr); + vm.recordLogs(); + vm.startPrank(borrower.addr); + + consideration.matchAdvancedOrders(orders, new CriteriaResolver[](0), fill, address(borrower.addr)); + + (, loan) = abi.decode(vm.getRecordedLogs()[debt.length + 1].data, (uint256, LoanManager.Loan)); + + assertEq(erc721s[1].ownerOf(1), address(nlr.custodian)); + assertEq(erc20s[0].balanceOf(seller.addr), balanceBefore + x.parameters.consideration[0].startAmount); + vm.stopPrank(); + } - uint256 balanceBefore = erc20s[0].balanceOf(borrower.addr); - vm.recordLogs(); - vm.startPrank(borrower.addr); - consideration.fulfillAdvancedOrder({ - advancedOrder: x, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(borrower.addr) - }); - Vm.Log[] memory logs = vm.getRecordedLogs(); - uint256 loanId; + function _executeNLR(LoanManager.Obligation memory nlr, ConsiderationItem[] memory collateral) + internal + returns (LoanManager.Loan memory loan) + { + bytes32 caveatHash = + keccak256(LM.encodeWithSaltAndBorrowerCounter(nlr.borrower, nlr.salt, keccak256(abi.encode(nlr.caveats)))); + OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); + + for (uint256 i; i < debt.length;) { + offer[i] = OfferItem({ + itemType: debt[i].itemType, + token: debt[i].token, + identifierOrCriteria: debt[i].identifier, + startAmount: debt[i].amount, + endAmount: debt[i].amount + }); + unchecked { + ++i; + } + } + + offer[nlr.debt.length] = OfferItem({ + itemType: ItemType.ERC721, + token: address(LM), + identifierOrCriteria: uint256(caveatHash), + startAmount: 1, + endAmount: 1 + }); + + OrderParameters memory op = + _buildContractOrder(address(LM), nlr.caveats.length == 0 ? new OfferItem[](0) : offer, collateral); + + AdvancedOrder memory x = + AdvancedOrder({parameters: op, numerator: 1, denominator: 1, signature: "0x", extraData: abi.encode(nlr)}); + + uint256 balanceBefore = erc20s[0].balanceOf(borrower.addr); + vm.recordLogs(); + vm.startPrank(borrower.addr); + consideration.fulfillAdvancedOrder({ + advancedOrder: x, + criteriaResolvers: new CriteriaResolver[](0), + fulfillerConduitKey: bytes32(0), + recipient: address(borrower.addr) + }); + Vm.Log[] memory logs = vm.getRecordedLogs(); + uint256 loanId; + + // console.logBytes32(logs[logs.length - 4].topics[0]); + bytes32 lienOpenTopic = bytes32(0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].topics[0] == lienOpenTopic) { + (loanId, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); + break; + } + } + + uint256 balanceAfter = erc20s[0].balanceOf(borrower.addr); + + assertEq(balanceAfter - balanceBefore, debt[0].amount); + vm.stopPrank(); + } - // console.logBytes32(logs[logs.length - 4].topics[0]); - bytes32 lienOpenTopic = bytes32( - 0x57cb72d73c48fadf55428537f6c9efbe080ae111339b0c5af42d9027ed20ba17 - ); - for (uint i = 0; i < logs.length; i++) { - if (logs[i].topics[0] == lienOpenTopic) { - (loanId, loan) = abi.decode(logs[i].data, (uint256, LoanManager.Loan)); - break; - } + function _repayLoan(address borrower, uint256 amount, LoanManager.Loan memory loan) internal { + vm.startPrank(borrower); + erc20s[0].approve(address(consideration), amount); + vm.stopPrank(); + _executeRepayLoan(loan); } - uint256 balanceAfter = erc20s[0].balanceOf(borrower.addr); - - assertEq(balanceAfter - balanceBefore, debt[0].amount); - vm.stopPrank(); - } - - function _repayLoan(address borrower, uint256 amount, LoanManager.Loan memory loan) internal { - vm.startPrank(borrower); - erc20s[0].approve(address(consideration), amount); - vm.stopPrank(); - _executeRepayLoan(loan); - } - - function _createLoan721Collateral20Debt(address lender, uint256 borrowAmount, LoanManager.Terms memory terms) internal returns (LoanManager.Loan memory loan) { - uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); - assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); - uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - - loan = _createLoan({ - lender: lender, - terms: terms, - collateralItem: - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }), - debtItem: - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: borrowAmount, - identifier: 0 - }) - }); + function _createLoan721Collateral20Debt(address lender, uint256 borrowAmount, LoanManager.Terms memory terms) + internal + returns (LoanManager.Loan memory loan) + { + uint256 initial721Balance = erc721s[0].balanceOf(borrower.addr); + assertTrue(initial721Balance > 0, "Test must have at least one erc721 token"); + uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); + + loan = _createLoan({ + lender: lender, + terms: terms, + collateralItem: ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custodian)) + }), + debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: borrowAmount, identifier: 0}) + }); + + assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); + assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); + } - assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); - assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); - - } - - // TODO update or overload to take interest rate - function _createLoan20Collateral20Debt(address lender, uint256 collateralAmount, uint256 borrowAmount, LoanManager.Terms memory terms) internal returns (LoanManager.Loan memory loan) { - uint256 initial20Balance1 = erc20s[1].balanceOf(borrower.addr); - assertTrue(initial20Balance1 > 0, "Borrower must have at least one erc20 token"); - - uint256 initial20Balance0 = erc20s[0].balanceOf(borrower.addr); - - loan = _createLoan({ - lender: lender, - terms: terms, - collateralItem: - ConsiderationItem({ - token: address(erc20s[1]), - startAmount: collateralAmount, - endAmount: collateralAmount, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(address(custodian)) - }), - debtItem: - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: borrowAmount, - identifier: 0 - }) - }); + // TODO update or overload to take interest rate + function _createLoan20Collateral20Debt( + address lender, + uint256 collateralAmount, + uint256 borrowAmount, + LoanManager.Terms memory terms + ) internal returns (LoanManager.Loan memory loan) { + uint256 initial20Balance1 = erc20s[1].balanceOf(borrower.addr); + assertTrue(initial20Balance1 > 0, "Borrower must have at least one erc20 token"); + + uint256 initial20Balance0 = erc20s[0].balanceOf(borrower.addr); + + loan = _createLoan({ + lender: lender, + terms: terms, + collateralItem: ConsiderationItem({ + token: address(erc20s[1]), + startAmount: collateralAmount, + endAmount: collateralAmount, + identifierOrCriteria: 0, + itemType: ItemType.ERC20, + recipient: payable(address(custodian)) + }), + debtItem: SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: borrowAmount, identifier: 0}) + }); + + assertEq( + initial20Balance1 - collateralAmount, erc20s[1].balanceOf(borrower.addr), "Borrower ERC20 was not sent out" + ); + assertEq(initial20Balance0 + borrowAmount, erc20s[0].balanceOf(borrower.addr), "Borrower did not receive ERC20"); + } - assertEq(initial20Balance1 - collateralAmount, erc20s[1].balanceOf(borrower.addr), "Borrower ERC20 was not sent out"); - assertEq(initial20Balance0 + borrowAmount, erc20s[0].balanceOf(borrower.addr), "Borrower did not receive ERC20"); - } - - // TODO fix - function _createLoan20Collateral721Debt(address lender, LoanManager.Terms memory terms) internal returns (LoanManager.Loan memory loan) { - return _createLoan({ - lender: lender, - terms: terms, - collateralItem: - ConsiderationItem({ - token: address(erc20s[0]), - startAmount: 20, - endAmount: 20, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(address(custodian)) - }), - debtItem: - SpentItem({ - itemType: ItemType.ERC721, - token: address(erc721s[0]), - amount: 1, - identifier: 0 - }) - }); - } - - function _createLoan(address lender, LoanManager.Terms memory terms, ConsiderationItem memory collateralItem, SpentItem memory debtItem) internal returns (LoanManager.Loan memory loan) { - selectedCollateral.push(collateralItem); - debt.push(debtItem); - - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custodian), - issuer: lender, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); + // TODO fix + function _createLoan20Collateral721Debt(address lender, LoanManager.Terms memory terms) + internal + returns (LoanManager.Loan memory loan) + { + return _createLoan({ + lender: lender, + terms: terms, + collateralItem: ConsiderationItem({ + token: address(erc20s[0]), + startAmount: 20, + endAmount: 20, + identifierOrCriteria: 0, + itemType: ItemType.ERC20, + recipient: payable(address(custodian)) + }), + debtItem: SpentItem({itemType: ItemType.ERC721, token: address(erc721s[0]), amount: 1, identifier: 0}) + }); + } - loan = newLoan( - NewLoanData({ - custodian: address(custodian), - caveats: new LoanManager.Caveat[](0), // TODO check - details: abi.encode(loanDetails) - }), - Originator(UO), - selectedCollateral - ); - } + function _createLoan( + address lender, + LoanManager.Terms memory terms, + ConsiderationItem memory collateralItem, + SpentItem memory debtItem + ) internal returns (LoanManager.Loan memory loan) { + selectedCollateral.push(collateralItem); + debt.push(debtItem); + + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custodian), + issuer: lender, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); + + loan = newLoan( + NewLoanData({ + custodian: address(custodian), + caveats: new LoanManager.Caveat[](0), // TODO check + details: abi.encode(loanDetails) + }), + Originator(UO), + selectedCollateral + ); + } } diff --git a/test/TestAstariaV1Loan.sol b/test/TestAstariaV1Loan.sol index c324ca1c..82fef31a 100644 --- a/test/TestAstariaV1Loan.sol +++ b/test/TestAstariaV1Loan.sol @@ -7,230 +7,170 @@ import "forge-std/console2.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; contract TestAstariaV1Loan is AstariaV1Test { - using {StarPortLib.getId} for LoanManager.Loan; + using {StarPortLib.getId} for LoanManager.Loan; - function testNewLoanERC721CollateralDefaultTermsRecall() public { - Custodian custody = Custodian(LM.defaultCustodian()); + function testNewLoanERC721CollateralDefaultTermsRecall() public { + Custodian custody = Custodian(LM.defaultCustodian()); - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custody)) + }) + ); - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 100, - identifier: 0 - }) - ); - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); + debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custody), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); - LoanManager.Loan memory loan = newLoan( - NewLoanData( - address(custody), - new LoanManager.Caveat[](0), - abi.encode(loanDetails) - ), - Originator(UO), - selectedCollateral - ); - uint256 loanId = loan.getId(); - assertTrue( - LM.active(loanId), - "LoanId not in active state after a new loan" - ); + LoanManager.Loan memory loan = newLoan( + NewLoanData(address(custody), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + Originator(UO), + selectedCollateral + ); + uint256 loanId = loan.getId(); + assertTrue(LM.active(loanId), "LoanId not in active state after a new loan"); - { - vm.startPrank(recaller.addr); - vm.expectRevert(BaseRecall.RecallBeforeHoneymoonExpiry.selector); - // attempt recall before honeymoon period has ended - BaseRecall(address(hook)).recall(loan, recallerConduit); - vm.stopPrank(); - } - { - // refinance with before recall is initiated - vm.startPrank(refinancer.addr); - vm.expectRevert(Pricing.InvalidRefinance.selector); - LM.refinance( - loan, - abi.encode( - BasePricing.Details({ - rate: (uint256(1e16) * 100) / (365 * 1 days), - carryRate: 0 - }) - ), - refinancerConduit - ); - vm.stopPrank(); - } - uint256 stake; - { - uint256 balanceBefore = erc20s[0].balanceOf(recaller.addr); - uint256 recallContractBalanceBefore = erc20s[0].balanceOf(address(hook)); - BaseRecall.Details memory details = abi.decode( - loan.terms.hookData, - (BaseRecall.Details) - ); - vm.warp(block.timestamp + details.honeymoon); - vm.startPrank(recaller.addr); + { + vm.startPrank(recaller.addr); + vm.expectRevert(BaseRecall.RecallBeforeHoneymoonExpiry.selector); + // attempt recall before honeymoon period has ended + BaseRecall(address(hook)).recall(loan, recallerConduit); + vm.stopPrank(); + } + { + // refinance with before recall is initiated + vm.startPrank(refinancer.addr); + vm.expectRevert(Pricing.InvalidRefinance.selector); + LM.refinance( + loan, + abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), + refinancerConduit + ); + vm.stopPrank(); + } + uint256 stake; + { + uint256 balanceBefore = erc20s[0].balanceOf(recaller.addr); + uint256 recallContractBalanceBefore = erc20s[0].balanceOf(address(hook)); + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + vm.warp(block.timestamp + details.honeymoon); + vm.startPrank(recaller.addr); - BaseRecall recallContract = BaseRecall(address(hook)); - recallContract.recall(loan, recallerConduit); - vm.stopPrank(); + BaseRecall recallContract = BaseRecall(address(hook)); + recallContract.recall(loan, recallerConduit); + vm.stopPrank(); - uint256 balanceAfter = erc20s[0].balanceOf(recaller.addr); - uint256 recallContractBalanceAfter = erc20s[0].balanceOf(address(hook)); + uint256 balanceAfter = erc20s[0].balanceOf(recaller.addr); + uint256 recallContractBalanceAfter = erc20s[0].balanceOf(address(hook)); - BasePricing.Details memory pricingDetails = abi.decode( - loan.terms.pricingData, - (BasePricing.Details) - ); - stake = BasePricing(address(pricing)).calculateInterest( - details.recallStakeDuration, - loan.debt[0].amount, - pricingDetails.rate - ); - assertEq( - balanceBefore, - balanceAfter + stake, - "Recaller balance not transfered correctly" - ); - assertEq( - recallContractBalanceBefore + stake, - recallContractBalanceAfter, - "Balance not transfered to recall contract correctly" - ); - } - { - BaseRecall recallContract = BaseRecall(address(hook)); - address recallerAddr; - uint64 start; - (recallerAddr, start) = recallContract.recalls(loanId); + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + stake = BasePricing(address(pricing)).calculateInterest( + details.recallStakeDuration, loan.debt[0].amount, pricingDetails.rate + ); + assertEq(balanceBefore, balanceAfter + stake, "Recaller balance not transfered correctly"); + assertEq( + recallContractBalanceBefore + stake, + recallContractBalanceAfter, + "Balance not transfered to recall contract correctly" + ); + } + { + BaseRecall recallContract = BaseRecall(address(hook)); + address recallerAddr; + uint64 start; + (recallerAddr, start) = recallContract.recalls(loanId); - assertEq( - recaller.addr, - recallerAddr, - "Recaller address logged incorrectly" - ); - assertEq(start, block.timestamp, "Recall start logged incorrectly"); - } - { - BaseRecall recallContract = BaseRecall(address(hook)); - vm.expectRevert(BaseRecall.LoanHasNotBeenRefinanced.selector); - // attempt a withdraw without the loan being refinanced - recallContract.withdraw(loan, payable(address(this))); - } - { - // refinance with incorrect terms - vm.expectRevert(AstariaV1Pricing.InsufficientRefinance.selector); - vm.startPrank(refinancer.addr); - LM.refinance( - loan, - abi.encode( - BasePricing.Details({ - rate: (uint256(1e16) * 100) / (365 * 1 days), - carryRate: 0 - }) - ), - refinancerConduit - ); - vm.stopPrank(); - } - { - // refinance with correct terms - uint256 newLenderBefore = erc20s[0].balanceOf(refinancer.addr); - uint256 oldLenderBefore = erc20s[0].balanceOf(lender.addr); - uint256 recallerBefore = erc20s[0].balanceOf(recaller.addr); - BaseRecall.Details memory details = abi.decode( - loan.terms.hookData, - (BaseRecall.Details) - ); - vm.startPrank(refinancer.addr); - vm.warp(block.timestamp + (details.recallWindow / 2)); - LM.refinance( - loan, - abi.encode( - BasePricing.Details({rate: details.recallMax / 2, carryRate: 0}) - ), - refinancerConduit - ); - vm.stopPrank(); - uint256 delta_t = block.timestamp - loan.start; - BasePricing.Details memory pricingDetails = abi.decode( - loan.terms.pricingData, - (BasePricing.Details) - ); - uint256 interest = BasePricing(address(pricing)).calculateInterest( - delta_t, - loan.debt[0].amount, - pricingDetails.rate - ); - uint256 newLenderAfter = erc20s[0].balanceOf(refinancer.addr); - uint256 oldLenderAfter = erc20s[0].balanceOf(lender.addr); - assertEq( - oldLenderAfter, - oldLenderBefore + loan.debt[0].amount + interest, - "Payment to old lender calculated incorrectly" - ); - assertEq( - newLenderAfter, - newLenderBefore - (loan.debt[0].amount + interest + stake), - "Payment from new lender calculated incorrectly" - ); - assertEq( - recallerBefore + stake, - erc20s[0].balanceOf(recaller.addr), - "Recaller did not recover stake as expected" - ); - assertTrue( - LM.inactive(loanId), - "LoanId not properly flipped to inactive after refinance" - ); - } - { - uint256 withdrawerBalanceBefore = erc20s[0].balanceOf(address(this)); - uint256 recallContractBalanceBefore = erc20s[0].balanceOf(address(hook)); - BaseRecall recallContract = BaseRecall(address(hook)); + assertEq(recaller.addr, recallerAddr, "Recaller address logged incorrectly"); + assertEq(start, block.timestamp, "Recall start logged incorrectly"); + } + { + BaseRecall recallContract = BaseRecall(address(hook)); + vm.expectRevert(BaseRecall.LoanHasNotBeenRefinanced.selector); + // attempt a withdraw without the loan being refinanced + recallContract.withdraw(loan, payable(address(this))); + } + { + // refinance with incorrect terms + vm.expectRevert(AstariaV1Pricing.InsufficientRefinance.selector); + vm.startPrank(refinancer.addr); + LM.refinance( + loan, + abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), + refinancerConduit + ); + vm.stopPrank(); + } + { + // refinance with correct terms + uint256 newLenderBefore = erc20s[0].balanceOf(refinancer.addr); + uint256 oldLenderBefore = erc20s[0].balanceOf(lender.addr); + uint256 recallerBefore = erc20s[0].balanceOf(recaller.addr); + BaseRecall.Details memory details = abi.decode(loan.terms.hookData, (BaseRecall.Details)); + vm.startPrank(refinancer.addr); + vm.warp(block.timestamp + (details.recallWindow / 2)); + LM.refinance( + loan, abi.encode(BasePricing.Details({rate: details.recallMax / 2, carryRate: 0})), refinancerConduit + ); + vm.stopPrank(); + uint256 delta_t = block.timestamp - loan.start; + BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details)); + uint256 interest = + BasePricing(address(pricing)).calculateInterest(delta_t, loan.debt[0].amount, pricingDetails.rate); + uint256 newLenderAfter = erc20s[0].balanceOf(refinancer.addr); + uint256 oldLenderAfter = erc20s[0].balanceOf(lender.addr); + assertEq( + oldLenderAfter, + oldLenderBefore + loan.debt[0].amount + interest, + "Payment to old lender calculated incorrectly" + ); + assertEq( + newLenderAfter, + newLenderBefore - (loan.debt[0].amount + interest + stake), + "Payment from new lender calculated incorrectly" + ); + assertEq( + recallerBefore + stake, erc20s[0].balanceOf(recaller.addr), "Recaller did not recover stake as expected" + ); + assertTrue(LM.inactive(loanId), "LoanId not properly flipped to inactive after refinance"); + } + { + uint256 withdrawerBalanceBefore = erc20s[0].balanceOf(address(this)); + uint256 recallContractBalanceBefore = erc20s[0].balanceOf(address(hook)); + BaseRecall recallContract = BaseRecall(address(hook)); - // attempt a withdraw after the loan has been successfully refinanced - recallContract.withdraw(loan, payable(address(this))); - uint256 withdrawerBalanceAfter = erc20s[0].balanceOf(address(this)); - uint256 recallContractBalanceAfter = erc20s[0].balanceOf(address(hook)); - assertEq( - withdrawerBalanceBefore + stake, - withdrawerBalanceAfter, - "Withdrawer did not recover stake as expected" - ); - assertEq( - recallContractBalanceBefore - stake, - recallContractBalanceAfter, - "BaseRecall did not return the stake as expected" - ); + // attempt a withdraw after the loan has been successfully refinanced + recallContract.withdraw(loan, payable(address(this))); + uint256 withdrawerBalanceAfter = erc20s[0].balanceOf(address(this)); + uint256 recallContractBalanceAfter = erc20s[0].balanceOf(address(hook)); + assertEq( + withdrawerBalanceBefore + stake, withdrawerBalanceAfter, "Withdrawer did not recover stake as expected" + ); + assertEq( + recallContractBalanceBefore - stake, + recallContractBalanceAfter, + "BaseRecall did not return the stake as expected" + ); + } } - } } diff --git a/test/TestExoticLoans.t.sol b/test/TestExoticLoans.t.sol index 0e881e09..9ddc1045 100644 --- a/test/TestExoticLoans.t.sol +++ b/test/TestExoticLoans.t.sol @@ -23,8 +23,8 @@ contract TestExoticLoans is StarPortTest { hookData: swapHookData }); -// uint256 initialErc201balance = erc20s[1].balanceOf(borrower.addr); -// uint256 initialErc202balance = erc20s[0].balanceOf(borrower.addr); + // uint256 initialErc201balance = erc20s[1].balanceOf(borrower.addr); + // uint256 initialErc202balance = erc20s[0].balanceOf(borrower.addr); LoanManager.Loan memory loan = _createLoan20Collateral20Debt({ lender: lender.addr, @@ -33,15 +33,15 @@ contract TestExoticLoans is StarPortTest { terms: terms }); -// assertEq(erc20s[1].balanceOf(borrower.addr), initialErc201balance); -// assertEq(erc20s[0].balanceOf(borrower.addr), initialErc202balance); -// skip(10 days); -// -// _repayLoan({ -// borrower: borrower.addr, -// amount: 375, -// loan: loan -// }); + // assertEq(erc20s[1].balanceOf(borrower.addr), initialErc201balance); + // assertEq(erc20s[0].balanceOf(borrower.addr), initialErc202balance); + // skip(10 days); + // + // _repayLoan({ + // borrower: borrower.addr, + // amount: 375, + // loan: loan + // }); } } @@ -52,43 +52,43 @@ contract SwapHook is SettlementHook { } contract SwapHandler is SettlementHandler { - constructor(LoanManager LM_) SettlementHandler(LM_) {} - function execute( - LoanManager.Loan calldata loan - ) external override returns (bytes4) { + function execute(LoanManager.Loan calldata loan) external override returns (bytes4) { return bytes4(0); } - function validate( - LoanManager.Loan calldata loan - ) external view override returns (bool) { + function validate(LoanManager.Loan calldata loan) external view override returns (bool) { return true; } - function getSettlement( - LoanManager.Loan memory loan - ) external override returns (ReceivedItem[] memory consideration, address restricted) { + function getSettlement(LoanManager.Loan memory loan) + external + override + returns (ReceivedItem[] memory consideration, address restricted) + { return (new ReceivedItem[](0), address(0)); } } contract SwapPricing is Pricing { - constructor(LoanManager LM_) Pricing(LM_) {} - function getPaymentConsideration( - LoanManager.Loan memory loan - ) public view override returns (ReceivedItem[] memory, ReceivedItem[] memory) { + function getPaymentConsideration(LoanManager.Loan memory loan) + public + view + override + returns (ReceivedItem[] memory, ReceivedItem[] memory) + { return (new ReceivedItem[](0), new ReceivedItem[](0)); } - function isValidRefinance( - LoanManager.Loan memory loan, - bytes memory newPricingData, - address caller - ) external view override returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory) { + function isValidRefinance(LoanManager.Loan memory loan, bytes memory newPricingData, address caller) + external + view + override + returns (ReceivedItem[] memory, ReceivedItem[] memory, ReceivedItem[] memory) + { return (new ReceivedItem[](0), new ReceivedItem[](0), new ReceivedItem[](0)); } } diff --git a/test/TestLoanCombinations.t.sol b/test/TestLoanCombinations.t.sol index a6cfa886..2ba29eff 100644 --- a/test/TestLoanCombinations.t.sol +++ b/test/TestLoanCombinations.t.sol @@ -1,14 +1,15 @@ import "./StarPortTest.sol"; import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol"; -import { LibString } from "solady/src/utils/LibString.sol"; +import {LibString} from "solady/src/utils/LibString.sol"; -import { StarPortLib } from "src/lib/StarPortLib.sol"; +import {StarPortLib} from "src/lib/StarPortLib.sol"; import "forge-std/console.sol"; contract TestLoanCombinations is StarPortTest { using {StarPortLib.getId} for LoanManager.Loan; // TODO test liquidations + function testLoan721for20SimpleInterestDutchFixedRepay() public { LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(fixedTermHook), @@ -24,25 +25,17 @@ contract TestLoanCombinations is StarPortTest { uint256 initial20Balance = erc20s[0].balanceOf(borrower.addr); - LoanManager.Loan memory loan = _createLoan721Collateral20Debt({ - lender: lender.addr, - borrowAmount: 100, - terms: terms - }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); assertTrue(erc721s[0].balanceOf(borrower.addr) < initial721Balance, "Borrower ERC721 was not sent out"); assertTrue(erc20s[0].balanceOf(borrower.addr) > initial20Balance, "Borrower did not receive ERC20"); - - uint256 loanId = loan.getId(); + uint256 loanId = loan.getId(); assertTrue(LM.active(loanId), "LoanId not in active state after a new loan"); skip(10 days); - _repayLoan({ - borrower: borrower.addr, - amount: 375, - loan: loan - }); + _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); } function testLoan20for20SimpleInterestDutchFixedRepay() public { @@ -61,35 +54,35 @@ contract TestLoanCombinations is StarPortTest { terms: terms }); -// skip(10 days); -// -// _repayLoan({ -// borrower: borrower.addr, -// amount: 375, -// loan: loan -// }); + // skip(10 days); + // + // _repayLoan({ + // borrower: borrower.addr, + // amount: 375, + // loan: loan + // }); } function testLoan20For721SimpleInterestDutchFixedRepay() public { -// LoanManager.Terms memory terms = LoanManager.Terms({ -// hook: address(fixedTermHook), -// handler: address(dutchAuctionHandler), -// pricing: address(simpleInterestPricing), -// pricingData: defaultPricingData, -// handlerData: defaultHandlerData, -// hookData: defaultHookData -// }); -// LoanManager.Loan memory loan = _createLoan20Collateral721Debt({ -// lender: lender.addr, -// terms: terms -// }); -// skip(10 days); -// -// _repayLoan({ // TODO different repay -// borrower: borrower.addr, -// amount: 375, -// loan: loan -// }); + // LoanManager.Terms memory terms = LoanManager.Terms({ + // hook: address(fixedTermHook), + // handler: address(dutchAuctionHandler), + // pricing: address(simpleInterestPricing), + // pricingData: defaultPricingData, + // handlerData: defaultHandlerData, + // hookData: defaultHookData + // }); + // LoanManager.Loan memory loan = _createLoan20Collateral721Debt({ + // lender: lender.addr, + // terms: terms + // }); + // skip(10 days); + // + // _repayLoan({ // TODO different repay + // borrower: borrower.addr, + // amount: 375, + // loan: loan + // }); } function testLoanAstariaSettlementRepay() public { @@ -105,29 +98,22 @@ contract TestLoanCombinations is StarPortTest { handlerData: astariaSettlementHandlerData, hookData: astariaSettlementHookData }); - LoanManager.Loan memory loan = _createLoan721Collateral20Debt({ - lender: lender.addr, - borrowAmount: 100, - terms: terms - }); -// skip(10 days); -// -// _repayLoan({ -// borrower: borrower.addr, -// amount: 375, -// loan: loan -// }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); + // skip(10 days); + // + // _repayLoan({ + // borrower: borrower.addr, + // amount: 375, + // loan: loan + // }); } function testLoanSimpleInterestEnglishFixed() public { uint256[] memory reservePrice = new uint256[](1); reservePrice[0] = 300; - bytes memory englishAuctionHandlerData = abi.encode( - EnglishAuctionHandler.Details({ - reservePrice: reservePrice, - window: 7 days - }) - ); + bytes memory englishAuctionHandlerData = + abi.encode(EnglishAuctionHandler.Details({reservePrice: reservePrice, window: 7 days})); LoanManager.Terms memory terms = LoanManager.Terms({ hook: address(fixedTermHook), @@ -137,17 +123,10 @@ contract TestLoanCombinations is StarPortTest { handlerData: englishAuctionHandlerData, hookData: defaultHookData }); - LoanManager.Loan memory loan = _createLoan721Collateral20Debt({ - lender: lender.addr, - borrowAmount: 100, - terms: terms - }); + LoanManager.Loan memory loan = + _createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms}); skip(10 days); - _repayLoan({ - borrower: borrower.addr, - amount: 375, - loan: loan - }); + _repayLoan({borrower: borrower.addr, amount: 375, loan: loan}); } } diff --git a/test/TestLoanManager.sol b/test/TestLoanManager.sol index ae8ce7e2..4e4a8e02 100644 --- a/test/TestLoanManager.sol +++ b/test/TestLoanManager.sol @@ -1,31 +1,29 @@ import "./StarPortTest.sol"; contract TestStorage { - uint256[] loans; + uint256[] loans; - function setupLoans(uint256 num) public { - for (uint256 i = 0; i < num; i++) { - loans.push(i); + function setupLoans(uint256 num) public { + for (uint256 i = 0; i < num; i++) { + loans.push(i); + } } - } - function hashLoans() public view returns (bytes32) { - return keccak256(abi.encode(loans)); - } + function hashLoans() public view returns (bytes32) { + return keccak256(abi.encode(loans)); + } } contract TestLoanManager is StarPortTest { - function testSupportsInterface() public { - assertTrue( - LM.supportsInterface(type(ContractOffererInterface).interfaceId) - ); - assertTrue(LM.supportsInterface(type(ERC721).interfaceId)); - } + function testSupportsInterface() public { + assertTrue(LM.supportsInterface(type(ContractOffererInterface).interfaceId)); + assertTrue(LM.supportsInterface(type(ERC721).interfaceId)); + } - function testStorage() public { - TestStorage ts = new TestStorage(); - ts.setupLoans(100000); - bytes32 hash = ts.hashLoans(); - console.logBytes32(hash); - } + function testStorage() public { + TestStorage ts = new TestStorage(); + ts.setupLoans(100000); + bytes32 hash = ts.hashLoans(); + console.logBytes32(hash); + } } diff --git a/test/TestNewLoan.sol b/test/TestNewLoan.sol index e4f0ce41..1beeecba 100644 --- a/test/TestNewLoan.sol +++ b/test/TestNewLoan.sol @@ -2,410 +2,322 @@ import "./StarPortTest.sol"; import {AstariaV1Pricing} from "src/pricing/AstariaV1Pricing.sol"; contract TestNewLoan is StarPortTest { - function testNewLoanERC721CollateralDefaultTerms2() - public - returns (LoanManager.Loan memory) - { - Custodian custody = Custodian(LM.defaultCustodian()); - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); + function testNewLoanERC721CollateralDefaultTerms2() public returns (LoanManager.Loan memory) { + Custodian custody = Custodian(LM.defaultCustodian()); + + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custody)) + }) + ); + + debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custody), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); + bool isTrusted = true; + + TermEnforcer TE = new TermEnforcer(); + + TermEnforcer.Details memory TEDetails = + TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + + LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); + caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + + return + newLoan(NewLoanData(address(custody), caveats, abi.encode(loanDetails)), Originator(UO), selectedCollateral); + } - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 100, - identifier: 0 - }) - ); - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); - bool isTrusted = true; - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = TermEnforcer.Details({ - pricing: address(pricing), - hook: address(hook), - handler: address(handler) - }); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({ - enforcer: address(TE), - terms: abi.encode(TEDetails) - }); - - return - newLoan( - NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - Originator(UO), - selectedCollateral - ); - } - - function testNewLoanERC721CollateralDefaultTermsRefinance() public { - Custodian custody = Custodian(LM.defaultCustodian()); - - // pricing = new AstariaV1Pricing(LM); - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); + function testNewLoanERC721CollateralDefaultTermsRefinance() public { + Custodian custody = Custodian(LM.defaultCustodian()); + + // pricing = new AstariaV1Pricing(LM); + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custody)) + }) + ); + + debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custody), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); + bool isTrusted = true; + + LoanManager.Loan memory loan = newLoan( + NewLoanData(address(custody), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + Originator(UO), + selectedCollateral + ); + vm.startPrank(refinancer.addr); + LM.refinance( + loan, + abi.encode(BasePricing.Details({rate: (uint256(1e16) * 100) / (365 * 1 days), carryRate: 0})), + refinancerConduit + ); + vm.stopPrank(); + } - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 100, - identifier: 0 - }) - ); - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); - bool isTrusted = true; - - LoanManager.Loan memory loan = newLoan( - NewLoanData( - address(custody), - new LoanManager.Caveat[](0), - abi.encode(loanDetails) - ), - Originator(UO), - selectedCollateral - ); - vm.startPrank(refinancer.addr); - LM.refinance( - loan, - abi.encode( - BasePricing.Details({ - rate: (uint256(1e16) * 100) / (365 * 1 days), - carryRate: 0 - }) - ), - refinancerConduit - ); - vm.stopPrank(); - } - - function testNewLoanERC721CollateralDefaultTermsWithMerkleProof() - public - returns (LoanManager.Loan memory) - { - Custodian custody = Custodian(LM.defaultCustodian()); - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); + function testNewLoanERC721CollateralDefaultTermsWithMerkleProof() public returns (LoanManager.Loan memory) { + Custodian custody = Custodian(LM.defaultCustodian()); + + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custody)) + }) + ); + + debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + //need to add1 + //address custodian; + // address conduit; + // address issuer; + // uint256 deadline; + // LoanManager.Terms terms; + // SpentItem[] collateral; + // SpentItem[] debt; + // bytes validator; + MerkleOriginator.Details memory loanDetails = MerkleOriginator.Details({ + custodian: address(custody), + conduit: address(CP.conduit()), + issuer: address(CP), + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt, + validator: bytes("0x") + }); + bytes32 leafHash = keccak256( + abi.encode( + loanDetails.custodian, + loanDetails.conduit, + loanDetails.issuer, + loanDetails.deadline, + loanDetails.terms, + loanDetails.collateral, + loanDetails.debt + ) + ); + loanDetails.validator = abi.encode(MerkleOriginator.MerkleProof({root: leafHash, proof: new bytes32[](0)})); + + return newLoanWithMerkleProof( + NewLoanData(address(custody), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + Originator(MO), + selectedCollateral + ); + } - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 100, - identifier: 0 - }) - ); - //need to add1 - //address custodian; - // address conduit; - // address issuer; - // uint256 deadline; - // LoanManager.Terms terms; - // SpentItem[] collateral; - // SpentItem[] debt; - // bytes validator; - MerkleOriginator.Details memory loanDetails = MerkleOriginator.Details({ - custodian: address(custody), - conduit: address(CP.conduit()), - issuer: address(CP), - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt, - validator: bytes("0x") - }); - bytes32 leafHash = keccak256( - abi.encode( - loanDetails.custodian, - loanDetails.conduit, - loanDetails.issuer, - loanDetails.deadline, - loanDetails.terms, - loanDetails.collateral, - loanDetails.debt - ) - ); - loanDetails.validator = abi.encode( - MerkleOriginator.MerkleProof({root: leafHash, proof: new bytes32[](0)}) - ); + function testBuyNowPayLater() public { + ConsiderationItem[] memory want = new ConsiderationItem[](1); + want[0] = ConsiderationItem({ + token: address(erc20s[0]), + startAmount: 150, + endAmount: 150, + identifierOrCriteria: 0, + itemType: ItemType.ERC20, + recipient: payable(seller.addr) + }); + + //order 1, which lets is the selller, they have something that we can borrower againt (ERC721) + //order 2 which is the + + OfferItem[] memory sellingNFT = new OfferItem[](1); + sellingNFT[0] = OfferItem({ + identifierOrCriteria: 1, + token: address(erc721s[1]), + startAmount: 1, + endAmount: 1, + itemType: ItemType.ERC721 + }); + OrderParameters memory thingToSell = OrderParameters({ + offerer: seller.addr, + zone: address(0), + offer: sellingNFT, + consideration: want, + orderType: OrderType.FULL_OPEN, + startTime: block.timestamp, + endTime: block.timestamp + 150, + zoneHash: bytes32(0), + salt: 0, + conduitKey: bytes32(0), + totalOriginalConsiderationItems: 1 + }); + bytes32 sellingHash = consideration.getOrderHash(OrderParametersLib.toOrderComponents(thingToSell, 0)); + (bytes32 r, bytes32 s, uint8 v) = getSignatureComponents(consideration, seller.key, sellingHash); + + AdvancedOrder memory advThingToSell = AdvancedOrder({ + parameters: thingToSell, + numerator: 1, + denominator: 1, + signature: abi.encodePacked(r, s, v), + extraData: "" + }); + + Custodian custody = Custodian(LM.defaultCustodian()); + + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); + + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[1]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custody)) + }) + ); + + debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custody), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); + + TermEnforcer TE = new TermEnforcer(); + + TermEnforcer.Details memory TEDetails = + TermEnforcer.Details({pricing: address(pricing), hook: address(hook), handler: address(handler)}); + + LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); + caveats[0] = LoanManager.Caveat({enforcer: address(TE), terms: abi.encode(TEDetails)}); + + buyNowPayLater( + advThingToSell, + NewLoanData(address(custody), caveats, abi.encode(loanDetails)), + Originator(UO), + selectedCollateral + ); + } - return - newLoanWithMerkleProof( - NewLoanData( - address(custody), - new LoanManager.Caveat[](0), - abi.encode(loanDetails) - ), - Originator(MO), - selectedCollateral - ); - } - - function testBuyNowPayLater() public { - ConsiderationItem[] memory want = new ConsiderationItem[](1); - want[0] = ConsiderationItem({ - token: address(erc20s[0]), - startAmount: 150, - endAmount: 150, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(seller.addr) - }); - - //order 1, which lets is the selller, they have something that we can borrower againt (ERC721) - //order 2 which is the - - OfferItem[] memory sellingNFT = new OfferItem[](1); - sellingNFT[0] = OfferItem({ - identifierOrCriteria: 1, - token: address(erc721s[1]), - startAmount: 1, - endAmount: 1, - itemType: ItemType.ERC721 - }); - OrderParameters memory thingToSell = OrderParameters({ - offerer: seller.addr, - zone: address(0), - offer: sellingNFT, - consideration: want, - orderType: OrderType.FULL_OPEN, - startTime: block.timestamp, - endTime: block.timestamp + 150, - zoneHash: bytes32(0), - salt: 0, - conduitKey: bytes32(0), - totalOriginalConsiderationItems: 1 - }); - bytes32 sellingHash = consideration.getOrderHash( - OrderParametersLib.toOrderComponents(thingToSell, 0) - ); - (bytes32 r, bytes32 s, uint8 v) = getSignatureComponents( - consideration, - seller.key, - sellingHash - ); + function testSettleLoan() public { + //default is 14 day term + LoanManager.Loan memory activeLoan = testNewLoanERC721CollateralDefaultTerms2(); - AdvancedOrder memory advThingToSell = AdvancedOrder({ - parameters: thingToSell, - numerator: 1, - denominator: 1, - signature: abi.encodePacked(r, s, v), - extraData: "" - }); - - Custodian custody = Custodian(LM.defaultCustodian()); - - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); - - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[1]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custody)) - }) - ); + skip(14 days); - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 100, - identifier: 0 - }) - ); - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custody), - issuer: lender.addr, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); - - TermEnforcer TE = new TermEnforcer(); - - TermEnforcer.Details memory TEDetails = TermEnforcer.Details({ - pricing: address(pricing), - hook: address(hook), - handler: address(handler) - }); - - LoanManager.Caveat[] memory caveats = new LoanManager.Caveat[](1); - caveats[0] = LoanManager.Caveat({ - enforcer: address(TE), - terms: abi.encode(TEDetails) - }); - - buyNowPayLater( - advThingToSell, - NewLoanData(address(custody), caveats, abi.encode(loanDetails)), - Originator(UO), - selectedCollateral - ); - } - - function testSettleLoan() public { - //default is 14 day term - LoanManager.Loan - memory activeLoan = testNewLoanERC721CollateralDefaultTerms2(); - - skip(14 days); - - minimumReceived.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: 600 ether, - identifier: 0 - }) - ); - ( - ReceivedItem[] memory settlementConsideration, - address restricted - ) = SettlementHandler(activeLoan.terms.handler).getSettlement(activeLoan); + minimumReceived.push( + SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 600 ether, identifier: 0}) + ); + (ReceivedItem[] memory settlementConsideration, address restricted) = + SettlementHandler(activeLoan.terms.handler).getSettlement(activeLoan); - ConsiderationItem[] memory consider = new ConsiderationItem[]( + ConsiderationItem[] memory consider = new ConsiderationItem[]( settlementConsideration.length ); - uint i = 0; - for (; i < settlementConsideration.length; ) { - consider[i].token = settlementConsideration[i].token; - consider[i].itemType = settlementConsideration[i].itemType; - consider[i].identifierOrCriteria = settlementConsideration[i].identifier; - consider[i].startAmount = settlementConsideration[i].amount; - //TODO: update this - consider[i].endAmount = settlementConsideration[i].amount; - consider[i].recipient = settlementConsideration[i].recipient; - unchecked { - ++i; - } - } - OfferItem[] memory repayOffering = new OfferItem[]( + uint256 i = 0; + for (; i < settlementConsideration.length;) { + consider[i].token = settlementConsideration[i].token; + consider[i].itemType = settlementConsideration[i].itemType; + consider[i].identifierOrCriteria = settlementConsideration[i].identifier; + consider[i].startAmount = settlementConsideration[i].amount; + //TODO: update this + consider[i].endAmount = settlementConsideration[i].amount; + consider[i].recipient = settlementConsideration[i].recipient; + unchecked { + ++i; + } + } + OfferItem[] memory repayOffering = new OfferItem[]( activeLoan.collateral.length ); - i = 0; - for (; i < activeLoan.collateral.length; ) { - repayOffering[i] = OfferItem({ - itemType: activeLoan.collateral[i].itemType, - token: address(activeLoan.collateral[i].token), - identifierOrCriteria: activeLoan.collateral[i].identifier, - endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 - ? activeLoan.collateral[i].amount - : 1, - startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 - ? activeLoan.collateral[i].amount - : 1 - }); - unchecked { - ++i; - } - } - - OrderParameters memory op = _buildContractOrder( - address(activeLoan.custodian), - repayOffering, - consider - ); - if (restricted == address(0)) { - AdvancedOrder memory settlementOrder = AdvancedOrder({ - numerator: 1, - denominator: 1, - parameters: op, - extraData: abi.encode(activeLoan), - signature: "" - }); - - consideration.fulfillAdvancedOrder({ - advancedOrder: settlementOrder, - criteriaResolvers: new CriteriaResolver[](0), - fulfillerConduitKey: bytes32(0), - recipient: address(0) - }); + i = 0; + for (; i < activeLoan.collateral.length;) { + repayOffering[i] = OfferItem({ + itemType: activeLoan.collateral[i].itemType, + token: address(activeLoan.collateral[i].token), + identifierOrCriteria: activeLoan.collateral[i].identifier, + endAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1, + startAmount: activeLoan.collateral[i].itemType != ItemType.ERC721 ? activeLoan.collateral[i].amount : 1 + }); + unchecked { + ++i; + } + } + + OrderParameters memory op = _buildContractOrder(address(activeLoan.custodian), repayOffering, consider); + if (restricted == address(0)) { + AdvancedOrder memory settlementOrder = AdvancedOrder({ + numerator: 1, + denominator: 1, + parameters: op, + extraData: abi.encode(activeLoan), + signature: "" + }); + + consideration.fulfillAdvancedOrder({ + advancedOrder: settlementOrder, + criteriaResolvers: new CriteriaResolver[](0), + fulfillerConduitKey: bytes32(0), + recipient: address(0) + }); + } } - } } diff --git a/test/TestRepayLoan.sol b/test/TestRepayLoan.sol index 34f56e90..2c14292e 100644 --- a/test/TestRepayLoan.sol +++ b/test/TestRepayLoan.sol @@ -1,61 +1,57 @@ import "./StarPortTest.sol"; contract TestRepayLoan is StarPortTest { - function testRepayLoan() public { - uint256 borrowAmount = 100; - LoanManager.Terms memory terms = LoanManager.Terms({ - hook: address(hook), - handler: address(handler), - pricing: address(pricing), - pricingData: defaultPricingData, - handlerData: defaultHandlerData, - hookData: defaultHookData - }); + function testRepayLoan() public { + uint256 borrowAmount = 100; + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: defaultPricingData, + handlerData: defaultHandlerData, + hookData: defaultHookData + }); - selectedCollateral.push( - ConsiderationItem({ - token: address(erc721s[0]), - startAmount: 1, - endAmount: 1, - identifierOrCriteria: 1, - itemType: ItemType.ERC721, - recipient: payable(address(custodian)) - }) - ); + selectedCollateral.push( + ConsiderationItem({ + token: address(erc721s[0]), + startAmount: 1, + endAmount: 1, + identifierOrCriteria: 1, + itemType: ItemType.ERC721, + recipient: payable(address(custodian)) + }) + ); - debt.push( - SpentItem({ - itemType: ItemType.ERC20, - token: address(erc20s[0]), - amount: borrowAmount, - identifier: 0 // 0 for ERC20 - }) - ); + debt.push( + SpentItem({ + itemType: ItemType.ERC20, + token: address(erc20s[0]), + amount: borrowAmount, + identifier: 0 // 0 for ERC20 + }) + ); - UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ - conduit: address(lenderConduit), - custodian: address(custodian), - issuer: lender.addr, - deadline: block.timestamp + 100, - terms: terms, - collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), - debt: debt - }); - bool isTrusted = false; + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custodian), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(selectedCollateral), + debt: debt + }); + bool isTrusted = false; - LoanManager.Loan memory activeLoan = newLoan( - NewLoanData( - address(custodian), - new LoanManager.Caveat[](0), - abi.encode(loanDetails) - ), - Originator(UO), - selectedCollateral - ); - vm.startPrank(borrower.addr); - skip(10 days); - erc20s[0].approve(address(consideration), 375); - vm.stopPrank(); - _executeRepayLoan(activeLoan); - } + LoanManager.Loan memory activeLoan = newLoan( + NewLoanData(address(custodian), new LoanManager.Caveat[](0), abi.encode(loanDetails)), + Originator(UO), + selectedCollateral + ); + vm.startPrank(borrower.addr); + skip(10 days); + erc20s[0].approve(address(consideration), 375); + vm.stopPrank(); + _executeRepayLoan(activeLoan); + } } diff --git a/test/TestUtils.sol b/test/TestUtils.sol index 13e7fc22..a0890862 100644 --- a/test/TestUtils.sol +++ b/test/TestUtils.sol @@ -3,154 +3,136 @@ pragma solidity =0.8.17; import "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; +import {ItemType, ReceivedItem, SpentItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import { - ItemType, - ReceivedItem, - SpentItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; -import { - ConsiderationItem, - AdvancedOrder, - CriteriaResolver, - OrderType + ConsiderationItem, + AdvancedOrder, + CriteriaResolver, + OrderType } from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Conduit} from "seaport-core/src/conduit/Conduit.sol"; -import { - ConduitController -} from "seaport-core/src/conduit/ConduitController.sol"; +import {ConduitController} from "seaport-core/src/conduit/ConduitController.sol"; import {StarPortLib} from "src/lib/StarPortLib.sol"; contract TestStarLiteUtils is Test { - TestContract testContract; - - function setUp() public { - testContract = new TestContract(); - } - - function testSpentToReceived() public { - SpentItem[] memory spentItems = new SpentItem[](2); - spentItems[0] = SpentItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4 - }); - - spentItems[1] = SpentItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4 - }); - - ReceivedItem[] memory consideration0 = testContract.spentToReceivedBoring( - spentItems, - address(1) - ); - - ReceivedItem[] memory consideration1 = testContract.spentToReceivedSexc( - spentItems, - address(1) - ); - - assertEq(consideration0.length, consideration1.length); - for (uint256 i = 0; i < consideration0.length; i++) { - assert(consideration0[i].itemType == consideration1[i].itemType); - assertEq(consideration0[i].token, consideration1[i].token); - assertEq(consideration0[i].identifier, consideration1[i].identifier); - assertEq(consideration0[i].amount, consideration1[i].amount); - assertEq(consideration0[i].recipient, consideration1[i].recipient); + TestContract testContract; + + function setUp() public { + testContract = new TestContract(); + } + + function testSpentToReceived() public { + SpentItem[] memory spentItems = new SpentItem[](2); + spentItems[0] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); + + spentItems[1] = SpentItem({itemType: ItemType.ERC20, token: address(2), identifier: 3, amount: 4}); + + ReceivedItem[] memory consideration0 = testContract.spentToReceivedBoring(spentItems, address(1)); + + ReceivedItem[] memory consideration1 = testContract.spentToReceivedSexc(spentItems, address(1)); + + assertEq(consideration0.length, consideration1.length); + for (uint256 i = 0; i < consideration0.length; i++) { + assert(consideration0[i].itemType == consideration1[i].itemType); + assertEq(consideration0[i].token, consideration1[i].token); + assertEq(consideration0[i].identifier, consideration1[i].identifier); + assertEq(consideration0[i].amount, consideration1[i].amount); + assertEq(consideration0[i].recipient, consideration1[i].recipient); + } } - } - - function testEncodeReceivedWithRecipient() public { - ReceivedItem[] memory receivedItems = new ReceivedItem[](2); - receivedItems[0] = ReceivedItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4, - recipient: payable(address(5)) - }); - - receivedItems[1] = ReceivedItem({ - itemType: ItemType.ERC20, - token: address(2), - identifier: 3, - amount: 4, - recipient: payable(address(6)) - }); - - ReceivedItem[] memory consideration0 = testContract - .encodeReceivedItemsWithRecipientBoring(receivedItems, address(1)); - - ReceivedItem[] memory consideration1 = testContract - .encodeReceivedItemsWithRecipientSexc(receivedItems, address(1)); - - assertEq(consideration0.length, consideration1.length); - for (uint256 i = 0; i < consideration0.length; i++) { - assert(consideration0[i].itemType == consideration1[i].itemType); - assertEq(consideration0[i].token, consideration1[i].token); - assertEq(consideration0[i].identifier, consideration1[i].identifier); - assertEq(consideration0[i].amount, consideration1[i].amount); - assertEq(consideration0[i].recipient, consideration1[i].recipient); + + function testEncodeReceivedWithRecipient() public { + ReceivedItem[] memory receivedItems = new ReceivedItem[](2); + receivedItems[0] = ReceivedItem({ + itemType: ItemType.ERC20, + token: address(2), + identifier: 3, + amount: 4, + recipient: payable(address(5)) + }); + + receivedItems[1] = ReceivedItem({ + itemType: ItemType.ERC20, + token: address(2), + identifier: 3, + amount: 4, + recipient: payable(address(6)) + }); + + ReceivedItem[] memory consideration0 = + testContract.encodeReceivedItemsWithRecipientBoring(receivedItems, address(1)); + + ReceivedItem[] memory consideration1 = + testContract.encodeReceivedItemsWithRecipientSexc(receivedItems, address(1)); + + assertEq(consideration0.length, consideration1.length); + for (uint256 i = 0; i < consideration0.length; i++) { + assert(consideration0[i].itemType == consideration1[i].itemType); + assertEq(consideration0[i].token, consideration1[i].token); + assertEq(consideration0[i].identifier, consideration1[i].identifier); + assertEq(consideration0[i].amount, consideration1[i].amount); + assertEq(consideration0[i].recipient, consideration1[i].recipient); + } } - } } contract TestContract { - using {StarPortLib.toReceivedItems} for SpentItem[]; - using {StarPortLib.encodeWithRecipient} for ReceivedItem[]; - - function encodeReceivedItemsWithRecipientSexc( - ReceivedItem[] calldata receivedItems, - address recipient - ) external pure returns (ReceivedItem[] memory consideration) { - return receivedItems.encodeWithRecipient(recipient); - } - - function encodeReceivedItemsWithRecipientBoring( - ReceivedItem[] calldata receivedItems, - address recipient - ) external pure returns (ReceivedItem[] memory consideration) { - consideration = new ReceivedItem[](receivedItems.length); - for (uint256 i = 0; i < receivedItems.length; ) { - consideration[i] = ReceivedItem({ - itemType: receivedItems[i].itemType, - token: receivedItems[i].token, - identifier: receivedItems[i].identifier, - amount: receivedItems[i].amount, - recipient: payable(recipient) - }); - unchecked { - ++i; - } + using {StarPortLib.toReceivedItems} for SpentItem[]; + using {StarPortLib.encodeWithRecipient} for ReceivedItem[]; + + function encodeReceivedItemsWithRecipientSexc(ReceivedItem[] calldata receivedItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + return receivedItems.encodeWithRecipient(recipient); + } + + function encodeReceivedItemsWithRecipientBoring(ReceivedItem[] calldata receivedItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + consideration = new ReceivedItem[](receivedItems.length); + for (uint256 i = 0; i < receivedItems.length;) { + consideration[i] = ReceivedItem({ + itemType: receivedItems[i].itemType, + token: receivedItems[i].token, + identifier: receivedItems[i].identifier, + amount: receivedItems[i].amount, + recipient: payable(recipient) + }); + unchecked { + ++i; + } + } + } + + function spentToReceivedSexc(SpentItem[] calldata spentItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + return spentItems.toReceivedItems(recipient); } - } - - function spentToReceivedSexc( - SpentItem[] calldata spentItems, - address recipient - ) external pure returns (ReceivedItem[] memory consideration) { - return spentItems.toReceivedItems(recipient); - } - - function spentToReceivedBoring( - SpentItem[] calldata spentItems, - address recipient - ) external pure returns (ReceivedItem[] memory consideration) { - consideration = new ReceivedItem[](spentItems.length); - for (uint256 i = 0; i < spentItems.length; ) { - consideration[i] = ReceivedItem({ - itemType: spentItems[i].itemType, - token: spentItems[i].token, - identifier: spentItems[i].identifier, - amount: spentItems[i].amount, - recipient: payable(recipient) - }); - unchecked { - ++i; - } + + function spentToReceivedBoring(SpentItem[] calldata spentItems, address recipient) + external + pure + returns (ReceivedItem[] memory consideration) + { + consideration = new ReceivedItem[](spentItems.length); + for (uint256 i = 0; i < spentItems.length;) { + consideration[i] = ReceivedItem({ + itemType: spentItems[i].itemType, + token: spentItems[i].token, + identifier: spentItems[i].identifier, + amount: spentItems[i].amount, + recipient: payable(recipient) + }); + unchecked { + ++i; + } + } } - } } diff --git a/test/utils/Bound.sol b/test/utils/Bound.sol index 1c5d39d5..83ae37e6 100644 --- a/test/utils/Bound.sol +++ b/test/utils/Bound.sol @@ -1,64 +1,47 @@ pragma solidity =0.8.17; -import { - ItemType, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Cast} from "test/utils/Cast.sol"; import "test/utils/FuzzStructs.sol" as Fuzz; import "forge-std/Test.sol"; abstract contract Bound is StdUtils { - using Cast for *; + using Cast for *; - function _boundItemType(uint8 itemType) internal pure returns (ItemType) { - return - _bound( - itemType, - uint8(ItemType.NATIVE), - uint8(ItemType.ERC1155_WITH_CRITERIA) - ).toItemType(); - } + function _boundItemType(uint8 itemType) internal pure returns (ItemType) { + return _bound(itemType, uint8(ItemType.NATIVE), uint8(ItemType.ERC1155_WITH_CRITERIA)).toItemType(); + } - function _boundSpentItem( - Fuzz.SpentItem memory input - ) internal pure returns (SpentItem memory ret) { - ret = SpentItem({ - itemType: _boundItemType(input.itemType), - token: input.token, - identifier: input.identifier, - amount: input.amount - }); - } + function _boundSpentItem(Fuzz.SpentItem memory input) internal pure returns (SpentItem memory ret) { + ret = SpentItem({ + itemType: _boundItemType(input.itemType), + token: input.token, + identifier: input.identifier, + amount: input.amount + }); + } - function _boundSpentItems( - Fuzz.SpentItem[] memory input - ) internal pure returns (SpentItem[] memory ret) { - ret = new SpentItem[](input.length); - for (uint256 i = 0; i < input.length; i++) { - ret[i] = _boundSpentItem(input[i]); + function _boundSpentItems(Fuzz.SpentItem[] memory input) internal pure returns (SpentItem[] memory ret) { + ret = new SpentItem[](input.length); + for (uint256 i = 0; i < input.length; i++) { + ret[i] = _boundSpentItem(input[i]); + } } - } - function _boundReceivedItem( - Fuzz.ReceivedItem memory input - ) internal pure returns (ReceivedItem memory ret) { - ret = ReceivedItem({ - itemType: _boundItemType(input.itemType), - token: input.token, - identifier: input.identifier, - amount: input.amount, - recipient: input.recipient - }); - } + function _boundReceivedItem(Fuzz.ReceivedItem memory input) internal pure returns (ReceivedItem memory ret) { + ret = ReceivedItem({ + itemType: _boundItemType(input.itemType), + token: input.token, + identifier: input.identifier, + amount: input.amount, + recipient: input.recipient + }); + } - function _boundReceivedItems( - Fuzz.ReceivedItem[] memory input - ) internal pure returns (ReceivedItem[] memory ret) { - ret = new ReceivedItem[](input.length); - for (uint256 i = 0; i < input.length; i++) { - ret[i] = _boundReceivedItem(input[i]); + function _boundReceivedItems(Fuzz.ReceivedItem[] memory input) internal pure returns (ReceivedItem[] memory ret) { + ret = new ReceivedItem[](input.length); + for (uint256 i = 0; i < input.length; i++) { + ret[i] = _boundReceivedItem(input[i]); + } } - } } diff --git a/test/utils/Cast.sol b/test/utils/Cast.sol index cbd67fec..02a3bc35 100644 --- a/test/utils/Cast.sol +++ b/test/utils/Cast.sol @@ -1,29 +1,25 @@ pragma solidity =0.8.17; -import { - ItemType, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import "test/utils/FuzzStructs.sol" as Fuzz; import "forge-std/Test.sol"; library Cast { - function toUint(uint8 input) internal pure returns (uint256 ret) { - assembly { - ret := input + function toUint(uint8 input) internal pure returns (uint256 ret) { + assembly { + ret := input + } } - } - function toUint(address input) internal pure returns (uint256 ret) { - assembly { - ret := input + function toUint(address input) internal pure returns (uint256 ret) { + assembly { + ret := input + } } - } - function toItemType(uint256 input) internal pure returns (ItemType ret) { - assembly { - ret := input + function toItemType(uint256 input) internal pure returns (ItemType ret) { + assembly { + ret := input + } } - } } diff --git a/test/utils/DeepEq.sol b/test/utils/DeepEq.sol index 97e910b8..d1290199 100644 --- a/test/utils/DeepEq.sol +++ b/test/utils/DeepEq.sol @@ -1,34 +1,27 @@ -import { - ItemType, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import {Cast} from "test/utils/Cast.sol"; import "test/utils/FuzzStructs.sol" as Fuzz; import "forge-std/Test.sol"; abstract contract DeepEq { - function _deepEq( - ReceivedItem[] memory a, - ReceivedItem[] memory b - ) internal pure { - assert(a.length == b.length); - for (uint256 i = 0; i < a.length; i++) { - assert(a[i].itemType == b[i].itemType); - assert(a[i].token == b[i].token); - assert(a[i].identifier == b[i].identifier); - assert(a[i].amount == b[i].amount); - assert(a[i].recipient == b[i].recipient); + function _deepEq(ReceivedItem[] memory a, ReceivedItem[] memory b) internal pure { + assert(a.length == b.length); + for (uint256 i = 0; i < a.length; i++) { + assert(a[i].itemType == b[i].itemType); + assert(a[i].token == b[i].token); + assert(a[i].identifier == b[i].identifier); + assert(a[i].amount == b[i].amount); + assert(a[i].recipient == b[i].recipient); + } } - } - function _deepEq(SpentItem[] memory a, SpentItem[] memory b) internal pure { - assert(a.length == b.length); - for (uint256 i = 0; i < a.length; i++) { - assert(a[i].itemType == b[i].itemType); - assert(a[i].token == b[i].token); - assert(a[i].identifier == b[i].identifier); - assert(a[i].amount == b[i].amount); + function _deepEq(SpentItem[] memory a, SpentItem[] memory b) internal pure { + assert(a.length == b.length); + for (uint256 i = 0; i < a.length; i++) { + assert(a[i].itemType == b[i].itemType); + assert(a[i].token == b[i].token); + assert(a[i].identifier == b[i].identifier); + assert(a[i].amount == b[i].amount); + } } - } } diff --git a/test/utils/Expect.sol b/test/utils/Expect.sol index c5beaf45..1ed14c14 100644 --- a/test/utils/Expect.sol +++ b/test/utils/Expect.sol @@ -1,34 +1,26 @@ -import { - ItemType, - SpentItem, - ReceivedItem -} from "seaport-types/src/lib/ConsiderationStructs.sol"; +import {ItemType, SpentItem, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol"; import "forge-std/Test.sol"; abstract contract Expect is Test { - ItemType MAX_ITEM_TYPE = ItemType.ERC1155_WITH_CRITERIA; + ItemType MAX_ITEM_TYPE = ItemType.ERC1155_WITH_CRITERIA; - function _expectRevert(SpentItem[] calldata items) internal { - bool expectRevert; - ItemType max = type(ItemType).max; - assembly { - let e := add(items.offset, mul(items.length, 0x80)) + function _expectRevert(SpentItem[] calldata items) internal { + bool expectRevert; + ItemType max = type(ItemType).max; + assembly { + let e := add(items.offset, mul(items.length, 0x80)) - for { - let i := items.offset - } lt(i, e) { - i := add(i, 0x80) - } { - let item := calldataload(i) - if gt(item, max) { - expectRevert := 1 - break + for { let i := items.offset } lt(i, e) { i := add(i, 0x80) } { + let item := calldataload(i) + if gt(item, max) { + expectRevert := 1 + break + } + } } - } - } - if (expectRevert) { - vm.expectRevert(); + if (expectRevert) { + vm.expectRevert(); + } } - } } diff --git a/test/utils/FuzzStructs.sol b/test/utils/FuzzStructs.sol index f72cc0c6..3b98758d 100644 --- a/test/utils/FuzzStructs.sol +++ b/test/utils/FuzzStructs.sol @@ -1,16 +1,16 @@ pragma solidity =0.8.17; struct ReceivedItem { - uint8 itemType; - address token; - uint256 identifier; - uint256 amount; - address payable recipient; + uint8 itemType; + address token; + uint256 identifier; + uint256 amount; + address payable recipient; } struct SpentItem { - uint8 itemType; - address token; - uint256 identifier; - uint256 amount; + uint8 itemType; + address token; + uint256 identifier; + uint256 amount; } diff --git a/yarn.lock b/yarn.lock index 6f01a42b..de9e6ee0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,16 @@ # yarn lockfile v1 +"@chainlink/contracts@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@chainlink/contracts/-/contracts-0.6.1.tgz#8842b57e755793cbdbcbc45277fb5d179c993e19" + integrity sha512-EuwijGexttw0UjfrW+HygwhQIrGAbqpf1ue28R55HhWMHBzphEH0PhWm8DQmFfj5OZNy8Io66N4L0nStkZ3QKQ== + dependencies: + "@eth-optimism/contracts" "^0.5.21" + "@openzeppelin/contracts" "~4.3.3" + "@openzeppelin/contracts-upgradeable" "^4.7.3" + "@openzeppelin/contracts-v0.7" "npm:@openzeppelin/contracts@v3.4.2" + "@chainsafe/as-sha256@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" @@ -45,6 +55,37 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@eth-optimism/contracts@^0.5.21": + version "0.5.40" + resolved "https://registry.yarnpkg.com/@eth-optimism/contracts/-/contracts-0.5.40.tgz#d13a04a15ea947a69055e6fc74d87e215d4c936a" + integrity sha512-MrzV0nvsymfO/fursTB7m/KunkPsCndltVgfdHaT1Aj5Vi6R/doKIGGkOofHX+8B6VMZpuZosKCMQ5lQuqjt8w== + dependencies: + "@eth-optimism/core-utils" "0.12.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + +"@eth-optimism/core-utils@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.12.0.tgz#6337e4599a34de23f8eceb20378de2a2de82b0ea" + integrity sha512-qW+7LZYCz7i8dRa7SRlUKIo1VBU8lvN0HeXCxJR+z+xtMzMQpPds20XJNCMclszxYQHkXY00fOT6GvFw9ZL6nw== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/providers" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bufio "^1.0.7" + chai "^4.3.4" + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -133,7 +174,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -236,7 +277,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -698,6 +739,21 @@ table "^6.8.0" undici "^5.14.0" +"@openzeppelin/contracts-upgradeable@^4.7.3": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.3.tgz#ff17a80fb945f5102571f8efecb5ce5915cc4811" + integrity sha512-jjaHAVRMrE4UuZNfDwjlLGDxTHWIOwTJS2ldnc278a0gevfXfPr8hxKEVBGFBE96kl2G3VHDZhUimw/+G3TG2A== + +"@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" + integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== + +"@openzeppelin/contracts@~4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.3.tgz#ff6ee919fc2a1abaf72b22814bfb72ed129ec137" + integrity sha512-tDBopO1c98Yk7Cv/PZlHqrvtVjlgK5R4J6jxLwoO7qxK4xqOiZG+zSkIvGFpPZ0ikc3QOED3plgdqjgNTnBc7g== + "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -795,6 +851,13 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.16.0": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.1.tgz#f7c8a686974e1536da0105466c4db6727311253c" + integrity sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw== + dependencies: + antlr4ts "^0.5.0-alpha.4" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1359,6 +1422,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufio@^1.0.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.2.1.tgz#8d4ab3ddfcd5faa90f996f922f9397d41cbaf2de" + integrity sha512-9oR3zNdupcg/Ge2sSHQF3GX+kmvL/fTPvD0nd5AGLq8SjUYnTz+SlFjK/GXidndbZtIj+pVKXiWeR9w6e9wKCA== + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -1418,6 +1486,19 @@ chai-as-promised@^7.1.1: dependencies: check-error "^1.0.2" +chai@^4.3.4: + version "4.3.8" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.8.tgz#40c59718ad6928da6629c70496fe990b2bb5b17c" + integrity sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^4.1.2" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + chai@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" @@ -2734,6 +2815,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3718,7 +3804,16 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -prettier@^2.3.1: +prettier-plugin-solidity@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" + integrity sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg== + dependencies: + "@solidity-parser/parser" "^0.16.0" + semver "^7.3.8" + solidity-comments-extractor "^0.0.7" + +prettier@^2.3.1, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -4076,6 +4171,13 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" +semver@^7.3.8: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -4166,6 +4268,11 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + solidity-coverage@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813"