diff --git a/classDiagram.svg b/classDiagram.svg index 7f9f1d88..dd511aed 100644 --- a/classDiagram.svg +++ b/classDiagram.svg @@ -4,701 +4,765 @@ - - + + UmlClassDiagram - + 1 - -<<Struct>> -Signature -src/Constants.sol - -r: bytes32 -s: bytes32 -v: uint8 + +<<Struct>> +Signature +src/Constants.sol + +r: bytes32 +s: bytes32 +v: uint8 0 - -<<Abstract>> -Constants -src/Constants.sol + +<<Abstract>> +Constants +src/Constants.sol 1->0 - - + + 2 - -Custodian -src/Custodian.sol - -Public: -   LM: LoanManager -   seaport: address - -Internal: -    _setOfferApprovals(offer: SpentItem[]) -External: -    ratifyOrder(offer: SpentItem[], consideration: ReceivedItem[], context: bytes, orderHashes: bytes32[], contractNonce: uint256): (ratifyOrderMagicValue: bytes4) <<onlySeaport>> -    generateOrder(fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) <<onlySeaport>> -    getSeaportMetadata(): (string, schemas: Schema[]) -    onERC1155Received(address, address, uint256, uint256, bytes): bytes4 -    onERC1155BatchReceived(address, address, uint256[], uint256[], bytes): bytes4 -Public: -    <<modifier>> onlySeaport() -    constructor(LM_: LoanManager, seaport_: address) -    supportsInterface(interfaceId: bytes4): bool -    previewOrder(caller: address, fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) -    onERC721Received(operator: address, from: address, tokenId: uint256, data: bytes): bytes4 + +Custodian +src/Custodian.sol + +Public: +   LM: LoanManager +   seaport: address + +Internal: +    _beforeApprovalsSetHook(fulfiller: address, maximumSpent: SpentItem[], context: bytes) +    _setOfferApprovals(offer: SpentItem[], target: address) +External: +    ratifyOrder(offer: SpentItem[], consideration: ReceivedItem[], context: bytes, orderHashes: bytes32[], contractNonce: uint256): (ratifyOrderMagicValue: bytes4) <<onlySeaport>> +    custody(consideration: ReceivedItem[], orderHashes: bytes32[], contractNonce: uint256, context: bytes): (selector: bytes4) +    generateOrder(fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) <<onlySeaport>> +    getSeaportMetadata(): (string, schemas: Schema[]) +    onERC1155Received(address, address, uint256, uint256, bytes): bytes4 +    onERC1155BatchReceived(address, address, uint256[], uint256[], bytes): bytes4 +Public: +    <<modifier>> onlySeaport() +    constructor(LM_: LoanManager, seaport_: address) +    supportsInterface(interfaceId: bytes4): bool +    previewOrder(caller: address, fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) +    onERC721Received(operator: address, from: address, tokenId: uint256, data: bytes): bytes4 + + + +2->2 + + 4 - -<<Enum>> -FieldFlags -src/LoanManager.sol - -INITIALIZED: 0 -ACTIVE: 1 -INACTIVE: 2 + +<<Enum>> +FieldFlags +src/LoanManager.sol + +INITIALIZED: 0 +ACTIVE: 1 +INACTIVE: 2 3 - -LoanManager -src/LoanManager.sol - -Public: -   custodian: address -   seaport: address - -Internal: -    _issued(tokenId: uint256): bool -    _emptyTerms(): Terms -    _fillObligationAndVerify(fulfiller: address, obligation: LoanManager.Obligation, maximumSpentFromBorrower: SpentItem[]): (offer: SpentItem[]) -    _issueLoanManager(loan: Loan, mint: bool) -    _setDebtApprovals(debt: SpentItem[]) -    _setOffer(debt: SpentItem[], loanHash: bytes32): (offer: SpentItem[]) -    _cmpBeforeAfter(balancesBefore: uint256[], balancesAfter: uint256[], expectedDeltaIncrease: SpentItem[]) -    _getBalance(target: address, itemType: ItemType, token: address, identifier: uint256): uint256 -    _getBalance(target: address, before: SpentItem[]): (balances: uint256[]) -External: -    getIssuer(loan: Loan): (payable: address) -    settle(tokenId: uint256) -    getSeaportMetadata(): (string, schemas: Schema[]) -    issue(loan: Loan) -    generateOrder(fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) <<onlySeaport>> -    ratifyOrder(offer: SpentItem[], consideration: ReceivedItem[], context: bytes, orderHashes: bytes32[], contractNonce: uint256): (ratifyOrderMagicValue: bytes4) <<onlySeaport>> -Public: -    <<payable>> transferFrom(from: address, to: address, tokenId: uint256) -    <<event>> Close(loanId: uint256) -    <<event>> Open(loanId: uint256, loan: LoanManager.Loan) -    <<event>> SeaportCompatibleContractDeployed() -    <<modifier>> onlySeaport() -    constructor() -    name(): string -    symbol(): string -    tokenURI(tokenId: uint256): string -    ownerOf(loanId: uint256): address -    previewOrder(caller: address, fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) -    supportsInterface(interfaceId: bytes4): bool + +LoanManager +src/LoanManager.sol + +Public: +   seaport: address +   defaultCustodian: address +   DEFAULT_CUSTODIAN_CODE_HASH: bytes32 + +Internal: +    _issued(tokenId: uint256): bool +    _callCustody(consideration: ReceivedItem[], orderHashes: bytes32[], contractNonce: uint256, context: bytes): (selector: bytes4) +    _fillObligationAndVerify(fulfiller: address, obligation: LoanManager.Obligation, maximumSpentFromBorrower: SpentItem[]): (offer: SpentItem[]) +    _issueLoanManager(loan: Loan, mint: bool) +    _setDebtApprovals(debt: SpentItem) +    _setOffer(debt: SpentItem[], loanHash: bytes32): (offer: SpentItem[]) +External: +    getIssuer(loan: Loan): (payable: address) +    settle(loan: Loan) +    getSeaportMetadata(): (string, schemas: Schema[]) +    issue(loan: Loan) +    generateOrder(fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) <<onlySeaport>> +    ratifyOrder(offer: SpentItem[], consideration: ReceivedItem[], context: bytes, orderHashes: bytes32[], contractNonce: uint256): (ratifyOrderMagicValue: bytes4) <<onlySeaport>> +Public: +    <<payable>> transferFrom(from: address, to: address, tokenId: uint256) +    <<event>> Close(loanId: uint256) +    <<event>> Open(loanId: uint256, loan: LoanManager.Loan) +    <<event>> SeaportCompatibleContractDeployed() +    <<event>> log(bytes32) +    <<modifier>> onlySeaport() +    constructor() +    name(): string +    symbol(): string +    active(loan: Loan): bool +    tokenURI(tokenId: uint256): string +    ownerOf(loanId: uint256): address +    previewOrder(caller: address, fulfiller: address, minimumReceived: SpentItem[], maximumSpent: SpentItem[], context: bytes): (offer: SpentItem[], consideration: ReceivedItem[]) +    supportsInterface(interfaceId: bytes4): bool - + 4->3 - - + + 5 - -<<Struct>> -Terms -src/LoanManager.sol - -hook: address -pricing: address -handler: address -pricingData: bytes -handlerData: bytes -hookData: bytes + +<<Struct>> +Terms +src/LoanManager.sol + +hook: address +pricing: address +handler: address +pricingData: bytes +handlerData: bytes +hookData: bytes - + 5->3 - - + + 6 - -<<Struct>> -Loan -src/LoanManager.sol - -start: uint256 -borrower: address -issuer: address -originator: address -collateral: SpentItem[] -debt: SpentItem[] -terms: Terms + +<<Struct>> +Loan +src/LoanManager.sol + +start: uint256 +custodian: address +borrower: address +issuer: address +originator: address +collateral: SpentItem[] +debt: SpentItem[] +terms: Terms - + 6->5 - - + + - + 6->3 - - + + 7 - -<<Struct>> -Obligation -src/LoanManager.sol - -hash: bytes32 -originator: address -isTrusted: bool -ask: Originator.Request + +<<Struct>> +Obligation +src/LoanManager.sol + +custodian: address +originator: address +borrower: address +isTrusted: bool +debt: SpentItem[] +hash: bytes32 +details: bytes +signature: bytes - + 7->3 - - + + 8 - -<<Enum>> -ContextErrors -src/LoanManager.sol - -BAD_ORIGINATION: 0 -INVALID_PAYMENT: 1 -LENGTH_MISMATCH: 2 -BORROWER_MISMATCH: 3 -COLLATERAL: 4 -ZERO_ADDRESS: 5 -INVALID_LOAN: 6 -INVALID_CONDUIT: 7 -INVALID_RESOLVER: 8 -INVALID_COLLATERAL: 9 + +<<Enum>> +ContextErrors +src/LoanManager.sol + +BAD_ORIGINATION: 0 +INVALID_PAYMENT: 1 +LENGTH_MISMATCH: 2 +BORROWER_MISMATCH: 3 +COLLATERAL: 4 +ZERO_ADDRESS: 5 +INVALID_LOAN: 6 +INVALID_CONDUIT: 7 +INVALID_RESOLVER: 8 +INVALID_COLLATERAL: 9 - + 8->3 - - + + - + 3->4 - - - - - -3->5 - - + + - + 3->6 - - + + - + 3->7 - - + + - + 3->3 - - + + - + -10 - -<<Struct>> -Details -src/handlers/DutchAuctionHandler.sol - -startingPrice: uint256 -endingPrice: uint256 -window: uint256 +9 + +<<Interface>> +IPool +src/custodians/AAVEPoolCustodian.sol + +External: +     supply(asset: address, amount: uint256, onBehalfOf: address, referralCode: uint16) +     withdraw(asset: address, amount: uint256, to: address): uint256 - + -9 - -DutchAuctionHandler -src/handlers/DutchAuctionHandler.sol - -External: -    getSettlement(loan: LoanManager.Loan, minimumReceived: SpentItem[]): (consideration: ReceivedItem[], restricted: address) -Public: -    constructor(LM_: LoanManager) +10 + +AAVEPoolCustodian +src/custodians/AAVEPoolCustodian.sol + +Public: +   pool: IPool + +Internal: +    _beforeApprovalsSetHook(fulfiller: address, maximumSpent: SpentItem[], context: bytes) +    _enter(token: address, amount: uint256) +    _exit(token: address, amount: uint256) +External: +    custody(consideration: ReceivedItem[], orderHashes: bytes32[], contractNonce: uint256, context: bytes): (selector: bytes4) +Public: +    constructor(LM_: LoanManager, seaport_: address, pool_: address) + + + +10->2 + + - + 10->9 - - + + - - -9->10 - - + + +10->10 + + 12 - -<<Struct>> -Details -src/handlers/EnglishAuctionHandler.sol - -reservePrice: uint256 -window: uint256 + +<<Struct>> +Details +src/handlers/DutchAuctionHandler.sol + +startingPrice: uint256 +endingPrice: uint256 +window: uint256 11 - -EnglishAuctionHandler -src/handlers/EnglishAuctionHandler.sol - -Public: -   consideration: ConsiderationInterface -   ENGLISH_AUCTION_ZONE: address - -External: -    execute(loan: LoanManager.Loan): (selector: bytes4) -    getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (consideration: ReceivedItem[], restricted: address) -    liquidate(loan: LoanManager.Loan) -Public: -    constructor(LM_: LoanManager, consideration_: ConsiderationInterface) + +DutchAuctionHandler +src/handlers/DutchAuctionHandler.sol + +External: +    getSettlement(loan: LoanManager.Loan, minimumReceived: SpentItem[]): (consideration: ReceivedItem[], restricted: address) +    validate(loan: LoanManager.Loan): bool +Public: +    constructor(LM_: LoanManager) - + 12->11 - - + + - + 11->12 - - + + - + -13 - -LenderRestrictedHandler -src/handlers/LenderRestrictedHandler.sol - -External: -    getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (ReceivedItem[], restricted: address) -Public: -    constructor(LM_: LoanManager) +14 + +<<Struct>> +Details +src/handlers/EnglishAuctionHandler.sol + +reservePrice: uint256[] +window: uint256 - + -14 - -<<Abstract>> -SettlementHandler -src/handlers/SettlementHandler.sol - -Public: -   LM: LoanManager - -External: -    <<abstract>> getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (consideration: ReceivedItem[], restricted: address) -    execute(loan: LoanManager.Loan): bytes4 -Public: -    constructor(LM_: LoanManager) - - - -14->14 - - +13 + +EnglishAuctionHandler +src/handlers/EnglishAuctionHandler.sol + +Public: +   consideration: ConsiderationInterface +   ENGLISH_AUCTION_ZONE: address + +External: +    validate(loan: LoanManager.Loan): bool +    execute(loan: LoanManager.Loan): (selector: bytes4) +    getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (consideration: ReceivedItem[], restricted: address) +    liquidate(loan: LoanManager.Loan) +Public: +    constructor(LM_: LoanManager, consideration_: ConsiderationInterface) + + + +14->13 + + + + + +13->14 + + 15 - -MockERC20 -src/hh_helpers/MockERC20.sol - -Public: -    constructor() + +LenderRestrictedHandler +src/handlers/LenderRestrictedHandler.sol + +External: +    getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (ReceivedItem[], restricted: address) +    validate(loan: LoanManager.Loan): bool +Public: +    constructor(LM_: LoanManager) 16 - -MockERC721 -src/hh_helpers/MockERC721.sol - -Public: -    constructor() - - - -18 - -<<Struct>> -Details -src/hooks/ChainlinkHook.sol - -feed: address -ltvRatio: uint256 + +<<Abstract>> +SettlementHandler +src/handlers/SettlementHandler.sol + +Public: +   LM: LoanManager + +External: +    <<abstract>> validate(loan: LoanManager.Loan): bool +    <<abstract>> getSettlement(loan: LoanManager.Loan, maximumSpent: SpentItem[]): (consideration: ReceivedItem[], restricted: address) +    execute(loan: LoanManager.Loan): bytes4 +Public: +    constructor(LM_: LoanManager) + + + +16->16 + + - + 17 - -ChainlinkHook -src/hooks/ChainlinkHook.sol - -External: -    isActive(loan: LoanManager.Loan): bool -Public: -    _getLatestPrice(details: Details): int256 - - - -18->17 - - + +MockERC20 +src/hh_helpers/MockERC20.sol + +Public: +    constructor() - - -17->18 - - + + +18 + +MockERC721 +src/hh_helpers/MockERC721.sol + +Public: +    constructor() 20 - -<<Struct>> -Details -src/hooks/FixedTermHook.sol - -loanDuration: uint256 + +<<Struct>> +Details +src/hooks/ChainlinkHook.sol + +feed: address +ltvRatio: uint256 19 - -FixedTermHook -src/hooks/FixedTermHook.sol - -External: -    isActive(loan: LoanManager.Loan): bool + +ChainlinkHook +src/hooks/ChainlinkHook.sol + +External: +    isActive(loan: LoanManager.Loan): bool +Public: +    _getLatestPrice(details: Details): int256 - + 20->19 - - + + - + 19->20 - - + + - + -21 - -<<Abstract>> -SettlementHook -src/hooks/SettlementHook.sol - -External: -    <<abstract>> isActive(loan: LoanManager.Loan): bool +22 + +<<Struct>> +Details +src/hooks/FixedTermHook.sol + +loanDuration: uint256 - + -22 - -<<Abstract>> -Lender -src/interfaces/Lender.sol - -External: -    onSettle(loan: LoanManager.Loan): (selector: bytes4) - - - -22->22 - - +21 + +FixedTermHook +src/hooks/FixedTermHook.sol + +External: +    isActive(loan: LoanManager.Loan): bool + + + +22->21 + + + + + +21->22 + + 23 - -<<Interface>> -TokenReceiverInterface -src/interfaces/TokenReceiverInterface.sol - -External: -     onERC721Received(operator: address, from: address, tokenId: uint256, data: bytes): bytes4 -     onERC1155Received(address, address, uint256, uint256, bytes): bytes4 -     onERC1155BatchReceived(address, address, uint256[], uint256[], bytes): bytes4 + +<<Abstract>> +SettlementHook +src/hooks/SettlementHook.sol + +External: +    <<abstract>> isActive(loan: LoanManager.Loan): bool 24 - -<<Library>> -StarLiteLib -src/lib/StarLiteLib.sol - -Internal: -    toReceivedItems(spentItems: SpentItem[], recipient: address): (result: ReceivedItem[]) -    encodeWithRecipient(receivedItems: ReceivedItem[], recipient: address): (result: ReceivedItem[]) + +<<Abstract>> +Lender +src/interfaces/Lender.sol + +External: +    onSettle(loan: LoanManager.Loan): (selector: bytes4) + + + +24->24 + + - + -26 - -<<Struct>> -Response -src/originators/Originator.sol - -terms: LoanManager.Terms -issuer: address -mint: bool +25 + +<<Interface>> +TokenReceiverInterface +src/interfaces/TokenReceiverInterface.sol + +External: +     onERC721Received(operator: address, from: address, tokenId: uint256, data: bytes): bytes4 +     onERC1155Received(address, address, uint256, uint256, bytes): bytes4 +     onERC1155BatchReceived(address, address, uint256[], uint256[], bytes): bytes4 - + + +26 + +<<Library>> +StarLiteLib +src/lib/StarLiteLib.sol + +Internal: +    toReceivedItems(spentItems: SpentItem[], recipient: address): (result: ReceivedItem[]) +    encodeWithRecipient(receivedItems: ReceivedItem[], recipient: address): (result: ReceivedItem[]) + + -25 - -<<Abstract>> -Originator -src/originators/Originator.sol - -Private: -   _counter: uint256 -Internal: -   _DOMAIN_SEPARATOR: bytes32 -Public: -   LM: LoanManager -   EIP_DOMAIN: bytes32 -   ORIGINATOR_DETAILS_TYPEHASH: bytes32 -   VERSION: bytes32 -   strategist: address -   strategistFee: uint256 - -Internal: -    _packageTransfers(loan: SpentItem[], borrower: address, issuer: address): (transfers: ConduitTransfer[]) -    _validateSignature(hash: bytes32, signature: bytes) -External: -    <<abstract>> build(Request): Response -    <<abstract>> execute(Request): Response -    <<abstract>> getFeeConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem) -    incrementCounter() -Public: -    constructor(LM_: LoanManager, strategist_: address, fee_: uint256) -    encodeWithAccountCounter(account: address, contextHash: bytes32): bytes -    getStrategistData(): (address, uint256) -    getCounter(): uint256 -    domainSeparator(): bytes32 - - - -26->25 - - +28 + +<<Struct>> +Response +src/originators/Originator.sol + +terms: LoanManager.Terms +issuer: address +mint: bool - + 27 - -<<Struct>> -Request -src/originators/Originator.sol - -borrower: address -debt: SpentItem[] -details: bytes -signature: bytes - - - -27->25 - - - - + +<<Abstract>> +Originator +src/originators/Originator.sol + +Private: +   _counter: uint256 +Internal: +   _DOMAIN_SEPARATOR: bytes32 +Public: +   LM: LoanManager +   EIP_DOMAIN: bytes32 +   ORIGINATOR_DETAILS_TYPEHASH: bytes32 +   VERSION: bytes32 +   strategist: address +   strategistFee: uint256 + +Internal: +    _packageTransfers(loan: SpentItem[], borrower: address, issuer: address): (transfers: ConduitTransfer[]) +    _validateSignature(hash: bytes32, signature: bytes) +External: +    <<abstract>> terms(bytes): LoanManager.Terms +    <<abstract>> execute(Request): Response +    <<abstract>> getFeeConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem) +    incrementCounter() +Public: +    constructor(LM_: LoanManager, strategist_: address, fee_: uint256) +    encodeWithAccountCounter(account: address, contextHash: bytes32): bytes +    getStrategistData(): (address, uint256) +    getCounter(): uint256 +    domainSeparator(): bytes32 + + -25->26 - - - - - -25->27 - - - - - -30 - -<<Struct>> -Details -src/originators/UniqueOriginator.sol - -conduit: address -issuer: address -deadline: uint256 -terms: LoanManager.Terms -collateral: SpentItem[] -debt: SpentItem +28->27 + + - + 29 - -UniqueOriginator -src/originators/UniqueOriginator.sol - -Internal: -    _build(params: Request, details: Details): (response: Response) -External: -    execute(params: Request): (response: Response) -    getFeeConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem) -Public: -    <<event>> Origination(loanId: uint256, issuer: address, nlrDetails: bytes) -    constructor(LM_: LoanManager, strategist_: address, fee_: uint256) -    build(params: Request): (response: Response) - - - -30->29 - - - - - -31 - -<<Enum>> -State -src/originators/UniqueOriginator.sol - -INITIALIZED: 0 -CLOSED: 1 - - + +<<Struct>> +Request +src/originators/Originator.sol + +custodian: address +receiver: address +collateral: SpentItem[] +debt: SpentItem[] +details: bytes +signature: bytes + + -31->29 - - +29->27 + + - + -29->30 - - +27->28 + + - - -34 - -<<Struct>> -Details -src/pricing/FixedTermPricing.sol - -rate: uint256 -loanDuration: uint256 + + +27->29 + + - + + +32 + +<<Struct>> +Details +src/originators/UniqueOriginator.sol + +custodian: address +conduit: address +issuer: address +deadline: uint256 +terms: LoanManager.Terms +collateral: SpentItem[] +debt: SpentItem[] + + +31 + +UniqueOriginator +src/originators/UniqueOriginator.sol + +Internal: +    _build(params: Request, details: Details): (response: Response) +    _validateAsk(request: Request, details: Details) +External: +    execute(params: Request): (response: Response) +    getFeeConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem) +Public: +    <<event>> Origination(loanId: uint256, issuer: address, nlrDetails: bytes) +    constructor(LM_: LoanManager, strategist_: address, fee_: uint256) +    terms(details: bytes): LoanManager.Terms + + + +32->31 + + + + + 33 - -FixedTermPricing -src/pricing/FixedTermPricing.sol - -Internal: -    _getOwed(loan: LoanManager.Loan, details: Details, timestamp: uint256): (updatedDebt: uint256[]) -    _getInterest(loan: LoanManager.Loan, details: Details, timestamp: uint256, index: uint256): uint256 -External: -    getPaymentConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) -Public: -    constructor(LM_: LoanManager) -    getOwed(loan: LoanManager.Loan): uint256[] - - + +<<Enum>> +State +src/originators/UniqueOriginator.sol + +INITIALIZED: 0 +CLOSED: 1 + + -34->33 - - +33->31 + + - - -33->34 - - + + +31->32 + + - + +36 + +<<Struct>> +Details +src/pricing/FixedTermPricing.sol + +rate: uint256 +loanDuration: uint256 + + + 35 - -<<Abstract>> -Pricing -src/pricing/Pricing.sol - -Public: -   LM: LoanManager -   x: uint256 - -Internal: -    _generateRepayLenderConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) -External: -    <<abstract>> getPaymentConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) -Public: -    <<abstract>> getOwed(loan: LoanManager.Loan): uint256[] -    constructor(LM_: LoanManager) + +FixedTermPricing +src/pricing/FixedTermPricing.sol + +Internal: +    _getOwed(loan: LoanManager.Loan, details: Details, timestamp: uint256): (updatedDebt: uint256[]) +    _getInterest(loan: LoanManager.Loan, details: Details, timestamp: uint256, index: uint256): uint256 +External: +    getPaymentConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) +Public: +    constructor(LM_: LoanManager) +    getOwed(loan: LoanManager.Loan): uint256[] + + + +36->35 + + + + + +35->36 + + + + + +37 + +<<Abstract>> +Pricing +src/pricing/Pricing.sol + +Public: +   LM: LoanManager +   x: uint256 + +Internal: +    _generateRepayLenderConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) +External: +    <<abstract>> getPaymentConsideration(loan: LoanManager.Loan): (consideration: ReceivedItem[]) +Public: +    <<abstract>> getOwed(loan: LoanManager.Loan): uint256[] +    constructor(LM_: LoanManager) diff --git a/src/Custodian.sol b/src/Custodian.sol index 1a1149a6..1b006b3e 100644 --- a/src/Custodian.sol +++ b/src/Custodian.sol @@ -63,19 +63,28 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface { uint256 contractNonce ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); - if (SettlementHandler(loan.terms.handler).execute(loan) != SettlementHandler.execute.selector) { - revert InvalidHandler(); - } - - LM.settle(loan); - + //ensure loan is valid against what we have to deliver to seaport ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; + // 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 + if (!LM.active(loan)) { + // on repayment handler for originator here? + // they can do post state follow up, but have no rentrancy capabilities here as we havent left seaport + } else { + if (SettlementHandler(loan.terms.handler).execute(loan) != SettlementHandler.execute.selector) { + revert InvalidHandler(); + } + LM.settle(loan); + } } - function custody(bytes calldata context) external virtual { - // if (msg.sender != address(LM)) { - // revert InvalidSender(); - // } + function custody( + ReceivedItem[] calldata consideration, + bytes32[] calldata orderHashes, + uint256 contractNonce, + bytes calldata context + ) external virtual returns (bytes4 selector) { + selector = Custodian.custody.selector; } /** @@ -93,48 +102,6 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface { SpentItem[] calldata maximumSpent, bytes calldata context // encoded based on the schemaID ) external onlySeaport returns (SpentItem[] memory offer, ReceivedItem[] memory consideration) { - (offer, consideration) = previewOrder(msg.sender, fulfiller, minimumReceived, maximumSpent, context); - - if (offer.length > 0) { - _setOfferApprovals(offer); - } - } - - function _setOfferApprovals(SpentItem[] memory offer) 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(address(seaport), true); - } else if (offer[i].itemType == ItemType.ERC721) { - ERC721(offer[i].token).setApprovalForAll(address(seaport), true); - } else if (offer[i].itemType == ItemType.ERC20) { - uint256 allowance = ERC20(offer[i].token).allowance(address(this), address(seaport)); - if (allowance != 0) { - ERC20(offer[i].token).approve(address(seaport), 0); - } - ERC20(offer[i].token).approve(address(seaport), 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) { LoanManager.Loan memory loan = abi.decode(context, (LoanManager.Loan)); offer = loan.collateral; ReceivedItem memory feeConsideration = Originator(loan.originator).getFeeConsideration(loan); @@ -164,6 +131,8 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface { ++i; } } + + LM.settle(loan); } else { address restricted; //add in originator fee @@ -173,6 +142,54 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface { 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 _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); + } + } + } + + /** + * @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 } function getSeaportMetadata() external pure returns (string memory, Schema[] memory schemas) { diff --git a/src/LoanManager.sol b/src/LoanManager.sol index c33349f5..846a24dc 100644 --- a/src/LoanManager.sol +++ b/src/LoanManager.sol @@ -29,6 +29,7 @@ contract LoanManager is ERC721, ContractOffererInterface { // address public feeRecipient; address public constant seaport = address(0x2e234DAe75C793f67A35089C9d99245E1C58470b); address public immutable defaultCustodian; + bytes32 public immutable DEFAULT_CUSTODIAN_CODE_HASH; // uint256 public fee; // uint256 private constant ONE_WORD = 0x20; @@ -99,7 +100,14 @@ contract LoanManager is ERC721, ContractOffererInterface { } constructor() { - defaultCustodian = address(new Custodian(this, seaport)); + address custodian = address(new Custodian(this, seaport)); + + bytes32 defaultCustodianCodeHash; + assembly { + defaultCustodianCodeHash := extcodehash(custodian) + } + defaultCustodian = custodian; + DEFAULT_CUSTODIAN_CODE_HASH = defaultCustodianCodeHash; emit SeaportCompatibleContractDeployed(); } @@ -121,6 +129,10 @@ contract LoanManager is ERC721, ContractOffererInterface { _; } + function active(Loan calldata loan) public view returns (bool) { + return _getExtraData(uint256(keccak256(abi.encode(loan)))) == uint8(FieldFlags.ACTIVE); + } + function tokenURI(uint256 tokenId) public view override returns (string memory) { return string(abi.encodePacked("https://astaria.xyz/loans?id=", tokenId)); } @@ -154,25 +166,33 @@ contract LoanManager is ERC721, ContractOffererInterface { if (_exists(tokenId)) { _burn(tokenId); } + _setExtraData(tokenId, uint8(FieldFlags.INACTIVE)); emit Close(tokenId); } - function _callCustody(bytes calldata data) internal returns (bool success) { + // + function _callCustody( + ReceivedItem[] calldata consideration, + bytes32[] calldata orderHashes, + uint256 contractNonce, + bytes calldata context + ) internal returns (bytes4 selector) { address custodian; assembly { - custodian := calldataload(add(data.offset, 0x20)) // 0x20 offset for the first address 'custodian' + custodian := calldataload(add(context.offset, 0x20)) // 0x20 offset for the first address 'custodian' } // Comparing the retrieved code hash with a known hash (placeholder here) - if (custodian != defaultCustodian) { - bytes4 functionSelector = bytes4(keccak256("custody(bytes)")); - bytes memory callData = abi.encodeWithSelector(functionSelector, data); - - assembly { - success := call(gas(), custodian, 0, add(callData, 0x20), mload(callData), 0, 0) - } - if (!success) { + 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(); } } @@ -319,7 +339,6 @@ contract LoanManager is ERC721, ContractOffererInterface { offer[0] = SpentItem({itemType: ItemType.ERC721, token: address(this), identifier: uint256(loanHash), amount: 1}); uint256 i = 0; - for (; i < debt.length;) { offer[i + 1] = debt[i]; _setDebtApprovals(debt[i]); @@ -350,7 +369,13 @@ contract LoanManager is ERC721, ContractOffererInterface { bytes32[] calldata orderHashes, uint256 contractNonce ) external onlySeaport returns (bytes4 ratifyOrderMagicValue) { - _callCustody(context); + //function custody( + // ReceivedItem[] calldata consideration, + // bytes32[] calldata orderHashes, + // uint256 contractNonce, + // bytes calldata context + // ) external override returns (bytes4 selector) + _callCustody(consideration, orderHashes, contractNonce, context); ratifyOrderMagicValue = ContractOffererInterface.ratifyOrder.selector; } diff --git a/src/custodians/AAVEPoolCustodian.sol b/src/custodians/AAVEPoolCustodian.sol new file mode 100644 index 00000000..313b5533 --- /dev/null +++ b/src/custodians/AAVEPoolCustodian.sol @@ -0,0 +1,46 @@ +pragma solidity =0.8.17; + +import {ERC20} from "solady/src/tokens/ERC20.sol"; +import "../Custodian.sol"; + +interface IPool { + function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external; + + function withdraw(address asset, uint256 amount, address to) external returns (uint256); +} + +contract AAVEPoolCustodian is Custodian { + IPool public pool; + + constructor(LoanManager LM_, address seaport_, address pool_) Custodian(LM_, seaport_) { + pool = IPool(pool_); + } + + //gets full seaport context + function custody( + ReceivedItem[] calldata consideration, + bytes32[] calldata orderHashes, + uint256 contractNonce, + bytes calldata context + ) external override returns (bytes4 selector) { + _enter(consideration[0].token, consideration[0].amount); + selector = AAVEPoolCustodian.custody.selector; + } + + function _beforeApprovalsSetHook(address fulfiller, SpentItem[] calldata maximumSpent, bytes calldata context) + internal + virtual + override + { + _exit(maximumSpent[0].token, maximumSpent[0].amount); + } + + function _enter(address token, uint256 amount) internal { + ERC20(token).approve(address(pool), amount); + pool.supply(token, amount, address(this), 0); + } + + function _exit(address token, uint256 amount) internal { + pool.withdraw(token, amount, address(this)); + } +} diff --git a/src/originators/Originator.sol b/src/originators/Originator.sol index 16164b8d..c6a65986 100644 --- a/src/originators/Originator.sol +++ b/src/originators/Originator.sol @@ -77,7 +77,6 @@ abstract contract Originator { ConduitItemType itemType; SpentItem memory debt = loan[i]; - //forge-fmt skip-next-line assembly { itemType := mload(debt) switch itemType diff --git a/test/TestStarLite.sol b/test/TestStarLite.sol index 47588afb..a659568d 100644 --- a/test/TestStarLite.sol +++ b/test/TestStarLite.sol @@ -13,17 +13,23 @@ import { 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 { 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 {Consideration} from "seaport-core/src/lib/Consideration.sol"; +//import { +// ReferenceConsideration as Consideration +//} from "seaport/reference/ReferenceConsideration.sol"; import {UniqueOriginator} from "src/originators/UniqueOriginator.sol"; import {FixedTermPricing} from "src/pricing/FixedTermPricing.sol"; import {FixedTermHook} from "src/hooks/FixedTermHook.sol"; @@ -42,10 +48,19 @@ 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 {Custodian} from "src/Custodian.sol"; +import "../src/custodians/AAVEPoolCustodian.sol"; +import "seaport/lib/seaport-sol/src/lib/AdvancedOrderLib.sol"; + +interface IWETH9 { + function deposit() external payable; + + function withdraw(uint256) external; +} contract TestStarLite is BaseOrderTest { Account borrower; Account lender; + Account seller; Account strategist; bytes32 conduitKey; @@ -57,6 +72,7 @@ contract TestStarLite is BaseOrderTest { function _deployAndConfigureConsideration() public { conduitController = new ConduitController(); + consideration = new Consideration(address(conduitController)); } @@ -84,17 +100,19 @@ contract TestStarLite is BaseOrderTest { borrower = makeAndAllocateAccount("borrower"); lender = makeAndAllocateAccount("lender"); strategist = makeAndAllocateAccount("strategist"); + seller = makeAndAllocateAccount("seller"); LM = new LoanManager(); custodian = new Custodian(LM, address(consideration)); UO = new UniqueOriginator(LM, strategist.addr, 1e16); vm.label(address(erc721s[0]), "Collateral NFT"); - vm.label(address(erc721s[1]), "Debt 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); @@ -123,10 +141,27 @@ contract TestStarLite is BaseOrderTest { FixedTermPricing pricing = new FixedTermPricing(LM); DutchAuctionHandler handler = new DutchAuctionHandler(LM); FixedTermHook hook = new FixedTermHook(); + + // address aaveWETHPool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + // deal eth, deposit into weth, approve aaveWETHPool + // IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + // deal(address(weth), address(borrower.addr), 1000); + // vm.prank(address(borrower.addr)); + // ERC20(address(weth)).approve(address(consideration), 1000 ether); + // weth.deposit{value: 1000 ether}(); + // weth.approve(aaveWETHPool, 1000 ether); + + // AAVEPoolCustodian custody = new AAVEPoolCustodian( + // LM, + // address(consideration), + // address(aaveWETHPool) + // ); + + Custodian custody = Custodian(LM.defaultCustodian()); bytes memory pricingData = abi.encode(FixedTermPricing.Details({rate: uint256((uint256(1e16) / 365) * 1 days), loanDuration: 10 days})); - address custody = LM.defaultCustodian(); + // address custody = LM.defaultCustodian(); bytes memory handlerData = abi.encode( DutchAuctionHandler.Details({startingPrice: uint256(500 ether), endingPrice: 100 wei, window: 7 days}) ); @@ -141,6 +176,18 @@ contract TestStarLite is BaseOrderTest { hookData: hookData }); + collateral721.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: 1, identifier: 0})); UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ conduit: address(lenderConduit), custodian: address(custody), @@ -152,9 +199,89 @@ contract TestStarLite is BaseOrderTest { }); bool isTrusted = true; + collateral20.push( + ConsiderationItem({ + token: address(erc20s[0]), //collateral token + startAmount: 100, + endAmount: 100, + identifierOrCriteria: 0, + itemType: ItemType.ERC20, + recipient: payable(address(custody)) + }) + ); + + newLoan(NewLoanData(address(custody), isTrusted, abi.encode(loanDetails)), Originator(UO), collateral721); + } + + 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) + }); + + 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: "" + }); + FixedTermPricing pricing = new FixedTermPricing(LM); + DutchAuctionHandler handler = new DutchAuctionHandler(LM); + FixedTermHook hook = new FixedTermHook(); + + Custodian custody = Custodian(LM.defaultCustodian()); + + bytes memory pricingData = + abi.encode(FixedTermPricing.Details({rate: uint256((uint256(1e16) / 365) * 1 days), loanDuration: 10 days})); + + // address custody = LM.defaultCustodian(); + bytes memory handlerData = abi.encode( + DutchAuctionHandler.Details({startingPrice: uint256(500 ether), endingPrice: 100 wei, window: 7 days}) + ); + bytes memory hookData = abi.encode(FixedTermHook.Details({loanDuration: 10 days})); + + LoanManager.Terms memory terms = LoanManager.Terms({ + hook: address(hook), + handler: address(handler), + pricing: address(pricing), + pricingData: pricingData, + handlerData: handlerData, + hookData: hookData + }); + collateral721.push( ConsiderationItem({ - token: address(erc721s[0]), + token: address(erc721s[1]), startAmount: 1, endAmount: 1, identifierOrCriteria: 1, @@ -163,22 +290,23 @@ contract TestStarLite is BaseOrderTest { }) ); - collateral20.push( - ConsiderationItem({ - token: address(erc20s[1]), //collateral token - startAmount: 100, - endAmount: 100, - identifierOrCriteria: 0, - itemType: ItemType.ERC20, - recipient: payable(address(custody)) - }) - ); debt.push(SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 100, identifier: 0})); - LoanManager.Loan memory activeLoan = newLoan( - NewLoanData(custody, isTrusted, abi.encode(loanDetails)), - ExternalCall(address(pricing), pricingData), - ExternalCall(address(handler), handlerData), - ExternalCall(address(hook), hookData) + UniqueOriginator.Details memory loanDetails = UniqueOriginator.Details({ + conduit: address(lenderConduit), + custodian: address(custody), + issuer: lender.addr, + deadline: block.timestamp + 100, + terms: terms, + collateral: ConsiderationItemLib.toSpentItemArray(collateral721), + debt: debt + }); + bool isTrusted = false; + + buyNowPayLater( + advThingToSell, + NewLoanData(address(custody), isTrusted, abi.encode(loanDetails)), + Originator(UO), + collateral721 ); } @@ -230,12 +358,8 @@ contract TestStarLite is BaseOrderTest { }); bool isTrusted = false; - LoanManager.Loan memory activeLoan = newLoan( - NewLoanData(address(custodian), isTrusted, abi.encode(loanDetails)), - ExternalCall(address(pricing), pricingData), - ExternalCall(address(handler), handlerData), - ExternalCall(address(hook), hookData) - ); + LoanManager.Loan memory activeLoan = + newLoan(NewLoanData(address(custodian), isTrusted, abi.encode(loanDetails)), Originator(UO), collateral721); vm.startPrank(borrower.addr); erc20s[0].approve(address(consideration), 100000); vm.stopPrank(); @@ -260,26 +384,25 @@ contract TestStarLite is BaseOrderTest { bytes details; } - function newLoan( - NewLoanData memory loanData, - ExternalCall memory pricing, - ExternalCall memory handler, - ExternalCall memory hook - ) internal returns (LoanManager.Loan memory) { + function newLoan(NewLoanData memory loanData, Originator originator, ConsiderationItem[] storage collateral) + internal + returns (LoanManager.Loan memory) + { bool isTrusted = loanData.isTrusted; { (uint8 v, bytes32 r, bytes32 s) = vm.sign( - strategist.key, keccak256(UO.encodeWithAccountCounter(strategist.addr, keccak256(loanData.details))) + 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(UO) : address(0), - terms: UO.terms(loanData.details), + originator: isTrusted ? address(originator) : address(0), + terms: originator.terms(loanData.details), debt: debt, - collateral: ConsiderationItemLib.toSpentItemArray(collateral721), + collateral: ConsiderationItemLib.toSpentItemArray(collateral), start: uint256(0) }); return _executeNLR( @@ -292,13 +415,57 @@ contract TestStarLite is BaseOrderTest { details: loanData.details, signature: abi.encodePacked(r, s, v), hash: keccak256(abi.encode(loan)), - originator: address(UO) + originator: address(originator) }), - collateral721 + collateral // for building contract offer ); } } + 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) + }); + + bytes32 loanTemplateHash = keccak256(abi.encode(loan)); + //sign loan hash with borrower key + // (uint8 v, bytes32 r, bytes32 s) = vm.sign(borrower.key, loanTemplateHash); + + // Note: TODO: if the borrower is the fulfiller we dont need to add the loan hash template into the order only when not the filler, + + _buyNowPLNLR( + thingToBuy, + loan, + LoanManager.Obligation({ + isTrusted: false, + custodian: address(loanData.custodian), + borrower: borrower.addr, + debt: debt, + details: loanData.details, + signature: abi.encodePacked(r, s, v), + hash: loanTemplateHash, + originator: address(originator) + }), + collateral // for building contract offer + ); + } + function _buildContractOrder(address offerer, OfferItem[] memory offer, ConsiderationItem[] memory consider) internal view @@ -329,7 +496,8 @@ contract TestStarLite is BaseOrderTest { 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].startAmount = 5 ether; + //TODO: update this consider[i].endAmount = 5 ether; consider[i].recipient = loanPayment[i].recipient; unchecked { @@ -378,77 +546,212 @@ contract TestStarLite is BaseOrderTest { vm.stopPrank(); } - // function _matchNLR( - // LoanManager.Obligation memory nlr, - // ConsiderationItem[] memory collateral - // ) internal { - // //use murky to create a tree that is good - // // CriteriaResolver[] memory resolver = new CriteriaResolver[](1); - // - // // Merkle memory merkle = Merkle({ - // // root: bytes32(0), - // // leaves: new bytes32[](1), - // // leafIndex: 0, - // // leafCount: 1, - // // depth: 0 - // // }); - // - // Originator.Response memory request = Originator(nlr.originator).build( - // Originator.ExecuteParams({ - // borrower: nlr.request.borrower, - // loanRequestHash: nlr.request.hash, - // details: nlr.request.details, - // debt: nlr.request.debt, - // signature: nlr.request.signature - // }) - // ); - // OfferItem[] memory offerItem = new OfferItem[](1); - // offerItem[0] = OfferItem({ - // itemType: ItemType.ERC721, - // token: address(LM), - // identifierOrCriteria: uint256(keccak256(abi.encode(request.loan))), - // startAmount: 1, - // endAmount: 1 - // }); - // - // OrderParameters memory op = _buildContractOrder( - // address(LM), - // offerItem, - // collateral - // ); - // bytes32 borrowerAskHash = consideration.getOrderHash(op); - // bytes memory signature = signOrder(consideration, borrower.key, orderHash); - // - // AdvancedOrder memory x = AdvancedOrder({ - // parameters: op, - // numerator: 1, - // denominator: 1, - // signature: signature, - // extraData: "" - // }); - // - // AdvancedOrder[] calldata orders = new AdvancedOrder[](2); - // orders[0] = x; - // orders[1] = y; - // // CriteriaResolver[] calldata criteriaResolvers, - // // Fulfillment[] calldata fulfillments, - // // address recipient - // - // uint256 balanceBefore = erc20s[0].balanceOf(borrower.addr); - // vm.recordLogs(); - // vm.startPrank(borrower.addr); - // consideration.matchAdvancedOrders({ - // 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 { + //use murky to create a tree that is good + + OfferItem[] memory offer = new OfferItem[](nlr.debt.length + 1); + offer[0] = OfferItem({ + itemType: ItemType.ERC721, + token: address(LM), + identifierOrCriteria: uint256(nlr.hash), + startAmount: 1, + endAmount: 1 + }); + uint256 i = 0; + for (; i < debt.length;) { + offer[i + 1] = OfferItem({ + itemType: debt[i].itemType, + token: debt[i].token, + identifierOrCriteria: debt[i].identifier, + startAmount: debt[i].amount, + endAmount: debt[i].amount + }); + unchecked { + ++i; + } + } + // bytes32 borrowerAskHash = consideration.getOrderHash(op); + // bytes memory signature = signOrder(consideration, borrower.key, orderHash); + + 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(nlr.hash), + startAmount: 1, + endAmount: 1, + recipient: payable(address(loanAsk.borrower)) + }); + OrderParameters memory zOP = OrderParameters({ + offerer: address(loanAsk.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; + + emit loga(orders[0]); + emit loga(orders[1]); + emit loga(orders[2]); + //# x is offering erc721 1 + // x is wanting 150 erc20 + // y is offering a loan for 100 erc20 + //# y is wanting erc721 1 as collateral + // z is offering 50 erc20 + // z wants 1 loan template + + //struct Fulfillment { + // FulfillmentComponent[] offerComponents; + // FulfillmentComponent[] considerationComponents; + //} + // + ///** + // * @dev Each fulfillment component contains one index referencing a specific + // * order and another referencing a specific offer or consideration item. + // */ + //struct FulfillmentComponent { + // uint256 orderIndex; + // uint256 itemIndex; + //} + + //using the above create an array that will fulfill the orders + + // 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: 1}); + fill[0].considerationComponents[0] = FulfillmentComponent({orderIndex: 0, itemIndex: 0}); + fill[1] = Fulfillment({ + offerComponents: new FulfillmentComponent[](1), + considerationComponents: new FulfillmentComponent[](1) + }); + + //collateral sent to custodian + 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) + }); + + //collateral sent to custodian + 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) + }); + + //collateral sent to custodian + fill[3].offerComponents[0] = FulfillmentComponent({orderIndex: 1, itemIndex: 0}); + fill[3].considerationComponents[0] = FulfillmentComponent({orderIndex: 2, itemIndex: 0}); + // fill[0].considerationComponents[1] = FulfillmentComponent({ + // orderIndex: 2, + // itemIndex: 0 + // }); + // + // // 0, 0 => 1, 0 + // // 1, 0 => 2, 0 + // // 1, 1 => 0, 0 + // // 2, 0 => 0, 0 + // + // //send debt to seller and loan template to borrower + // fill[1] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](2), + // considerationComponents: new FulfillmentComponent[](2) + // }); + // fill[1].offerComponents[0] = FulfillmentComponent({ + // orderIndex: 1, + // itemIndex: 0 + // }); + // fill[1].considerationComponents[0] = FulfillmentComponent({ + // orderIndex: 2, + // itemIndex: 0 + // }); + // + // fill[1].offerComponents[1] = FulfillmentComponent({ + // orderIndex: 1, + // itemIndex: 0 + // }); + // fill[1].considerationComponents[1] = FulfillmentComponent({ + // orderIndex: 0, + // itemIndex: 0 + // }); + // + // fill[2] = Fulfillment({ + // offerComponents: new FulfillmentComponent[](1), + // considerationComponents: new FulfillmentComponent[](1) + // }); + // fill[2].offerComponents[0] = FulfillmentComponent({ + // orderIndex: 2, + // itemIndex: 1 + // }); + // fill[2].considerationComponents[0] = FulfillmentComponent({ + // orderIndex: 0, + // itemIndex: 0 + // }); + + // uint256 balanceBefore = erc721s[1].balanceOf(borrower.addr); + // vm.recordLogs(); + vm.startPrank(borrower.addr); + + //AdvancedOrder[] calldata orders, + // CriteriaResolver[] calldata criteriaResolvers, + // Fulfillment[] calldata fulfillments, + // address recipient + // consideration.matchAdvancedOrders({ + // orders: orders, + // criteriaResolvers: new CriteriaResolver[](0), + // fufillments: fill, + // recipient: address(borrower.addr) + // }); + + consideration.matchAdvancedOrders(orders, new CriteriaResolver[](0), fill, address(borrower.addr)); + // Vm.Log[] memory logs = vm.getRecordedLogs(); + + // uint256 balanceAfter = erc721s[1].balanceOf(borrower.addr); + + vm.stopPrank(); + } + + event loga(AdvancedOrder); function _executeNLR( LoanManager.Loan memory ask, @@ -493,11 +796,14 @@ contract TestStarLite is BaseOrderTest { }); Vm.Log[] memory logs = vm.getRecordedLogs(); uint256 loanId; - (loanId, loan) = abi.decode(logs[logs.length - (nlr.isTrusted ? 3 : 4)].data, (uint256, LoanManager.Loan)); + (loanId, loan) = abi.decode( + logs[nlr.isTrusted ? debt.length : debt.length == 1 ? debt.length + 1 : debt.length * 2 + 1].data, + (uint256, LoanManager.Loan) + ); uint256 balanceAfter = erc20s[0].balanceOf(borrower.addr); - assertEq(balanceAfter - balanceBefore, 100); + assertEq(balanceAfter - balanceBefore, debt[0].amount); vm.stopPrank(); } }