Skip to content

Commit

Permalink
dynamic custodians & bnpl /w match testing
Browse files Browse the repository at this point in the history
- buy now pay later tests added using native seaport matchOrders
- custodians are now entirely customizable
  • Loading branch information
androolloyd committed Jun 19, 2023
1 parent fe5bd1f commit cf589ce
Show file tree
Hide file tree
Showing 6 changed files with 1,171 additions and 714 deletions.
1,140 changes: 602 additions & 538 deletions classDiagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 69 additions & 52 deletions src/Custodian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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);
Expand Down Expand Up @@ -164,6 +131,8 @@ contract Custodian is ContractOffererInterface, TokenReceiverInterface {
++i;
}
}

LM.settle(loan);
} else {
address restricted;
//add in originator fee
Expand All @@ -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) {
Expand Down
51 changes: 38 additions & 13 deletions src/LoanManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}

Expand All @@ -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));
}
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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;
}

Expand Down
46 changes: 46 additions & 0 deletions src/custodians/AAVEPoolCustodian.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
1 change: 0 additions & 1 deletion src/originators/Originator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit cf589ce

Please sign in to comment.