-
-
Save JeffCX/2d91b4f5f781f08a249350e748d85131 to your computer and use it in GitHub Desktop.
CreateOfferer.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: CC0-1.0 | |
pragma solidity ^0.8.21; | |
import {Test} from "forge-std/Test.sol"; | |
import {BaseSeaportTest} from "./base/BaseSeaportTest.t.sol"; | |
import {BaseLiquidDelegateTest} from "./base/BaseLiquidDelegateTest.t.sol"; | |
import {SeaportHelpers, User} from "./utils/SeaportHelpers.t.sol"; | |
import {IDelegateToken, Structs as IDelegateTokenStructs} from "src/interfaces/IDelegateToken.sol"; | |
import {IDelegateRegistry} from "delegate-registry/src/IDelegateRegistry.sol"; | |
import {AdvancedOrder, OrderParameters, Fulfillment, CriteriaResolver, OfferItem, ConsiderationItem, FulfillmentComponent} from "seaport/contracts/lib/ConsiderationStructs.sol"; | |
import {ItemType, OrderType} from "seaport/contracts/lib/ConsiderationEnums.sol"; | |
import {SpentItem} from "seaport/contracts/interfaces/ContractOffererInterface.sol"; | |
import {CreateOfferer, Enums as OffererEnums, Structs as OffererStructs} from "src/CreateOfferer.sol"; | |
import {TestContractOffererNativeToken} from "src/TestContractOffererNativeToken.sol"; | |
import {MockERC721} from "./mock/MockTokens.t.sol"; | |
import {WETH} from "./mock/WETH.t.sol"; | |
import {console2} from "forge-std/console2.sol"; | |
contract CreateOffererTest is Test, BaseSeaportTest, BaseLiquidDelegateTest, SeaportHelpers { | |
CreateOfferer createOfferer; | |
WETH weth; | |
uint256 startGas; | |
User buyer; | |
User seller; | |
OfferItem offerItem; | |
ConsiderationItem considerationItem; | |
OfferItem[] offerItems; | |
ConsiderationItem[] considerationItems; | |
SpentItem[] minimumReceived; | |
SpentItem[] maximumSpent; | |
function setUp() public { | |
OffererStructs.Parameters memory createOffererParameters = | |
OffererStructs.Parameters({seaport: address(seaport), delegateToken: address(dt), principalToken: address(principal)}); | |
createOfferer = new CreateOfferer(createOffererParameters); | |
weth = new WETH(); | |
// Setup buyer and seller | |
seller = makeUser("seller"); | |
vm.label(seller.addr, "seller"); | |
buyer = makeUser("buyer"); | |
vm.label(buyer.addr, "buyer"); | |
} | |
function addOfferItem(OfferItem memory _offerItem) internal { | |
offerItems.push(_offerItem); | |
} | |
///@dev reset the offer items array | |
function resetOfferItems() internal { | |
delete offerItems; | |
} | |
///@dev Construct and an offer item to the offer items array | |
function addOfferItem( | |
ItemType itemType, | |
address token, | |
uint256 identifier, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
offerItem.itemType = itemType; | |
offerItem.token = token; | |
offerItem.identifierOrCriteria = identifier; | |
offerItem.startAmount = startAmount; | |
offerItem.endAmount = endAmount; | |
addOfferItem(offerItem); | |
delete offerItem; | |
} | |
function addOfferItem( | |
ItemType itemType, | |
address token, | |
uint256 identifier, | |
uint256 amount | |
) internal { | |
addOfferItem({ | |
itemType: itemType, | |
token: token, | |
identifier: identifier, | |
startAmount: amount, | |
endAmount: amount | |
}); | |
} | |
function addOfferItem( | |
ItemType itemType, | |
uint256 identifier, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
if (itemType == ItemType.NATIVE) { | |
addEthOfferItem(startAmount, endAmount); | |
} else if (itemType == ItemType.ERC20) { | |
addErc20OfferItem(startAmount, endAmount); | |
} else if (itemType == ItemType.ERC1155) { | |
addErc1155OfferItem(identifier, startAmount, endAmount); | |
} else { | |
addErc721OfferItem(identifier); | |
} | |
} | |
function addOfferItem( | |
ItemType itemType, | |
uint256 identifier, | |
uint256 amt | |
) internal { | |
addOfferItem(itemType, identifier, amt, amt); | |
} | |
function addErc721OfferItem(uint256 identifier) internal { | |
addErc721OfferItem(address(mockERC721), identifier); | |
} | |
function addErc721OfferItem(address token, uint256 identifier) internal { | |
addErc721OfferItem(token, identifier, 1, 1); | |
} | |
function addErc721OfferItem( | |
address token, | |
uint256 identifier, | |
uint256 amount | |
) internal { | |
addErc721OfferItem(token, identifier, amount, amount); | |
} | |
function addErc721OfferItem( | |
address token, | |
uint256 identifier, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
addOfferItem( | |
ItemType.ERC721, | |
token, | |
identifier, | |
startAmount, | |
endAmount | |
); | |
} | |
function addErc1155OfferItem(uint256 tokenId, uint256 amount) internal { | |
addOfferItem( | |
ItemType.ERC1155, | |
address(mockERC1155), | |
tokenId, | |
amount, | |
amount | |
); | |
} | |
function addErc20OfferItem( | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
addOfferItem( | |
ItemType.ERC20, | |
address(mockERC20), | |
0, | |
startAmount, | |
endAmount | |
); | |
} | |
function addErc20OfferItem(uint256 amount) internal { | |
addErc20OfferItem(amount, amount); | |
} | |
function addErc1155OfferItem( | |
uint256 tokenId, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
addOfferItem( | |
ItemType.ERC1155, | |
address(mockERC1155), | |
tokenId, | |
startAmount, | |
endAmount | |
); | |
} | |
function addEthOfferItem(uint256 startAmount, uint256 endAmount) internal { | |
addOfferItem(ItemType.NATIVE, address(0), 0, startAmount, endAmount); | |
} | |
function addEthOfferItem(uint256 paymentAmount) internal { | |
addEthOfferItem(paymentAmount, paymentAmount); | |
} | |
///@dev add a considerationItem to the considerationItems array | |
function addConsiderationItem( | |
ConsiderationItem memory _considerationItem | |
) internal { | |
considerationItems.push(_considerationItem); | |
} | |
///@dev reset the considerationItems array | |
function resetConsiderationItems() internal { | |
delete considerationItems; | |
} | |
///@dev construct a considerationItem and add it to the considerationItems array | |
function addConsiderationItem( | |
address payable recipient, | |
ItemType itemType, | |
address token, | |
uint256 identifier, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
considerationItem.itemType = itemType; | |
considerationItem.token = token; | |
considerationItem.identifierOrCriteria = identifier; | |
considerationItem.startAmount = startAmount; | |
considerationItem.endAmount = endAmount; | |
considerationItem.recipient = recipient; | |
addConsiderationItem(considerationItem); | |
delete considerationItem; | |
} | |
function addConsiderationItem( | |
address payable recipient, | |
ItemType itemType, | |
uint256 identifier, | |
uint256 amt | |
) internal { | |
if (itemType == ItemType.NATIVE) { | |
addEthConsiderationItem(recipient, amt); | |
} else if (itemType == ItemType.ERC20) { | |
addErc20ConsiderationItem(recipient, amt); | |
} else if (itemType == ItemType.ERC1155) { | |
addErc1155ConsiderationItem(recipient, identifier, amt); | |
} else { | |
addErc721ConsiderationItem(recipient, identifier); | |
} | |
} | |
function addEthConsiderationItem( | |
address payable recipient, | |
uint256 paymentAmount | |
) internal { | |
addConsiderationItem( | |
recipient, | |
ItemType.NATIVE, | |
address(0), | |
0, | |
paymentAmount, | |
paymentAmount | |
); | |
} | |
function addEthConsiderationItem( | |
address payable recipient, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
addConsiderationItem( | |
recipient, | |
ItemType.NATIVE, | |
address(0), | |
0, | |
startAmount, | |
endAmount | |
); | |
} | |
function addErc20ConsiderationItem( | |
address payable receiver, | |
uint256 startAmount, | |
uint256 endAmount | |
) internal { | |
addConsiderationItem( | |
receiver, | |
ItemType.ERC20, | |
address(mockERC20), | |
0, | |
startAmount, | |
endAmount | |
); | |
} | |
function addErc20ConsiderationItem( | |
address payable receiver, | |
uint256 paymentAmount | |
) internal { | |
addErc20ConsiderationItem(receiver, paymentAmount, paymentAmount); | |
} | |
function addErc721ConsiderationItem( | |
address payable recipient, | |
uint256 tokenId | |
) internal { | |
addConsiderationItem( | |
recipient, | |
ItemType.ERC721, | |
address(mockERC721), | |
tokenId, | |
1, | |
1 | |
); | |
} | |
function addErc1155ConsiderationItem( | |
address payable recipient, | |
uint256 tokenId, | |
uint256 amount | |
) internal { | |
addConsiderationItem( | |
recipient, | |
ItemType.ERC1155, | |
address(mockERC1155), | |
tokenId, | |
amount, | |
amount | |
); | |
} | |
function test721OrderFilledByBuyer() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC721.setApprovalForAll(address(conduit), true); | |
// Define 721 order info and mint to seller, and deal expected eth to buyer | |
OffererStructs.ERC721Order memory erc721Order = OffererStructs.ERC721Order({ | |
tokenId: 42, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC721), | |
expiryLength: 30 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.principal | |
}) | |
}); | |
OfferItem memory offerItem = | |
OfferItem({itemType: ItemType.ERC721, token: erc721Order.info.tokenContract, identifierOrCriteria: erc721Order.tokenId, startAmount: 1, endAmount: 1}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC721, | |
token: erc721Order.info.tokenContract, | |
identifierOrCriteria: erc721Order.tokenId, | |
startAmount: 1, | |
endAmount: 1, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc721Order.info.rights, | |
signerSalt: erc721Order.info.signerSalt, | |
expiryLength: erc721Order.info.expiryLength, | |
expiryType: erc721Order.info.expiryType, | |
targetToken: erc721Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.3 ether; | |
vm.deal(buyer.addr, expectedETH); | |
mockERC721.mint(seller.addr, erc721Order.tokenId); | |
// Create order hash and id | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC721OrderHashAndId(seller.addr, address(conduit), erc721Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createSellerSignedOrder(seller, offerItem, createOrderHash, erc721Order.info.signerSalt, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createBuyerFillOrder(buyer, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller NFT => WrapReceipt, black line, order 0 offer item 0 matches with order 1 consideration item 0 | |
fulfillments[0] = _constructFulfillment(0, 0, 1, 0); | |
// Wrap Receipt => Seller, red line, order 1 offer item 0 matches with order 0 consideration item 1 | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 1); | |
// Buyer ETH => Seller, blue line, order 2 offer item 0 matches with order 0 consideration item 0 | |
// offer: (2, 0); consideration: (0, 0); (orderIndex, itemIndex) | |
fulfillments[2] = _constructFulfillment(2, 0, 0, 0); | |
// Match orders | |
vm.startPrank(buyer.addr); | |
_trackMatchOrderGasBefore(); | |
seaport.matchAdvancedOrders{value: expectedETH}(orders, new CriteriaResolver[](0), fulfillments, buyer.addr); | |
_trackMatchOrderGasAfter(); | |
vm.stopPrank(); | |
// Check | |
assertTrue(mockERC721.ownerOf(erc721Order.tokenId) == address(dt)); | |
assertEq(seller.addr.balance, expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc721Order.info.expiryLength); | |
} | |
function test721OfferFilledBySeller() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC721.setApprovalForAll(address(conduit), true); | |
vm.prank(buyer.addr); | |
weth.approve(address(conduit), type(uint256).max); | |
// Define 721 order info and mint token seller, and mint expected weth to buyer | |
OffererStructs.ERC721Order memory erc721Order = OffererStructs.ERC721Order({ | |
tokenId: 34, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC721), | |
expiryLength: block.timestamp + 40 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.delegate | |
}) | |
}); | |
OfferItem memory offerItem = OfferItem({itemType: ItemType.ERC721, token: address(mockERC721), identifierOrCriteria: erc721Order.tokenId, startAmount: 1, endAmount: 1}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC721, | |
token: erc721Order.info.tokenContract, | |
identifierOrCriteria: erc721Order.tokenId, | |
startAmount: 1, | |
endAmount: 1, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc721Order.info.rights, | |
signerSalt: erc721Order.info.signerSalt, | |
expiryLength: erc721Order.info.expiryLength, | |
expiryType: erc721Order.info.expiryType, | |
targetToken: erc721Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.22 ether; | |
weth.mint(buyer.addr, expectedETH); | |
mockERC721.mint(seller.addr, erc721Order.tokenId); | |
// Create order hash | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC721OrderHashAndId(buyer.addr, address(conduit), erc721Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createBuyerSignedOrder(buyer, createOrderHash, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createSellerFillOrder(seller, offerItem, erc721Order.info.signerSalt, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller NFT => Liquid Delegate V2 | |
fulfillments[0] = _constructFulfillment(2, 0, 1, 0); | |
// Wrap Receipt => Buyer | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 0); | |
// Buyer ETH => Seller | |
fulfillments[2] = _constructFulfillment(0, 0, 2, 0); | |
// Match orders | |
vm.startPrank(seller.addr); | |
_trackMatchOrderGasBefore(); | |
seaport.matchAdvancedOrders(orders, new CriteriaResolver[](0), fulfillments, seller.addr); | |
_trackMatchOrderGasAfter(); | |
// Check | |
assertTrue(mockERC721.ownerOf(erc721Order.tokenId) == address(dt)); | |
assertEq(weth.balanceOf(seller.addr), expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc721Order.info.expiryLength); | |
} | |
receive() external payable {} | |
function test_single_POC() public { | |
TestContractOffererNativeToken contractOfferer1 = new TestContractOffererNativeToken( | |
address(seaport), | |
"contractOffer1" | |
); | |
vm.deal(address(contractOfferer1), 100 ether); | |
mockERC721.setApprovalForAll(address(contractOfferer1), true); | |
mockERC721.setApprovalForAll(address(seaport), true); | |
mockERC721.mint(address(this), 1); | |
addEthOfferItem(1 ether); | |
addErc721ConsiderationItem( | |
payable(address(contractOfferer1)), | |
1 | |
); | |
OrderParameters memory orderParameters1 = OrderParameters( | |
address(contractOfferer1), | |
address(0), | |
offerItems, | |
considerationItems, | |
OrderType.CONTRACT, | |
block.timestamp, | |
block.timestamp + 1000, | |
bytes32(0), | |
0, | |
bytes32(0), | |
considerationItems.length | |
); | |
AdvancedOrder memory advancedOrder1 = AdvancedOrder( | |
orderParameters1, | |
1, | |
1, | |
"", | |
"" | |
); | |
FulfillmentComponent[][] memory offerFulfillments = new FulfillmentComponent[][](1); | |
offerFulfillments[0] = new FulfillmentComponent[](1); | |
offerFulfillments[0][0] = FulfillmentComponent(0, 0); | |
// Create considerationFulfillments 2D array similarly | |
FulfillmentComponent[][] memory considerationFulfillments = new FulfillmentComponent[][](1); | |
considerationFulfillments[0] = new FulfillmentComponent[](1); | |
considerationFulfillments[0][0] = FulfillmentComponent(0, 0); | |
AdvancedOrder[] memory orders = new AdvancedOrder[](1); | |
orders[0] = advancedOrder1; | |
seaport.fulfillAvailableAdvancedOrders( | |
orders, | |
new CriteriaResolver[](0), | |
offerFulfillments, | |
considerationFulfillments, | |
bytes32(0), | |
address(this), | |
1 | |
); | |
} | |
function test_POC() public { | |
TestContractOffererNativeToken contractOfferer1 = new TestContractOffererNativeToken( | |
address(seaport), | |
"contractOffer1" | |
); | |
vm.deal(address(contractOfferer1), 100 ether); | |
mockERC721.setApprovalForAll(address(contractOfferer1), true); | |
mockERC721.setApprovalForAll(address(seaport), true); | |
mockERC721.mint(address(this), 1); | |
addEthOfferItem(1 ether); | |
addErc721ConsiderationItem( | |
payable(address(contractOfferer1)), | |
1 | |
); | |
OrderParameters memory orderParameters1 = OrderParameters( | |
address(contractOfferer1), | |
address(0), | |
offerItems, | |
considerationItems, | |
OrderType.CONTRACT, | |
block.timestamp, | |
block.timestamp + 1000, | |
bytes32(0), | |
0, | |
bytes32(0), | |
considerationItems.length | |
); | |
AdvancedOrder memory advancedOrder1 = AdvancedOrder( | |
orderParameters1, | |
1, | |
1, | |
"", | |
"" | |
); | |
resetOfferItems(); | |
resetConsiderationItems(); | |
TestContractOffererNativeToken contractOfferer2 = new TestContractOffererNativeToken( | |
address(seaport), | |
"contractOffer2" | |
); | |
vm.deal(address(contractOfferer2), 100 ether); | |
mockERC721.setApprovalForAll(address(contractOfferer2), true); | |
mockERC721.setApprovalForAll(address(seaport), true); | |
mockERC721.mint(address(this), 2); | |
addEthOfferItem(1 ether); | |
addErc721ConsiderationItem( | |
payable(address(contractOfferer2)), | |
2 | |
); | |
contractOfferer1.setRevert(true); | |
contractOfferer2.setOtherContract(address(contractOfferer1)); | |
OrderParameters memory orderParameters2 = OrderParameters( | |
address(contractOfferer2), | |
address(0), | |
offerItems, | |
considerationItems, | |
OrderType.CONTRACT, | |
block.timestamp, | |
block.timestamp + 1000, | |
bytes32(0), | |
0, | |
bytes32(0), | |
considerationItems.length | |
); | |
AdvancedOrder memory advancedOrder2 = AdvancedOrder( | |
orderParameters2, | |
1, | |
1, | |
"", | |
"" | |
); | |
FulfillmentComponent[][] memory offerFulfillments = new FulfillmentComponent[][](2); | |
offerFulfillments[0] = new FulfillmentComponent[](2); | |
offerFulfillments[0][0] = FulfillmentComponent(0, 0); | |
offerFulfillments[0][1] = FulfillmentComponent(0, 0); | |
// Create considerationFulfillments 2D array similarly | |
FulfillmentComponent[][] memory considerationFulfillments = new FulfillmentComponent[][](2); | |
considerationFulfillments[0] = new FulfillmentComponent[](2); | |
considerationFulfillments[0][0] = FulfillmentComponent(0, 0); | |
considerationFulfillments[0][1] = FulfillmentComponent(0, 0); | |
AdvancedOrder[] memory orders = new AdvancedOrder[](2); | |
orders[0] = advancedOrder1; | |
orders[1] = advancedOrder2; | |
seaport.fulfillAvailableAdvancedOrders( | |
orders, | |
new CriteriaResolver[](0), | |
offerFulfillments, | |
considerationFulfillments, | |
bytes32(0), | |
address(this), | |
100 | |
); | |
} | |
function test20OrderFilledByBuyer() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC20.approve(address(conduit), type(uint256).max); | |
(string memory version, bytes32 a, address b) = seaport.information(); | |
console2.logString(version); | |
// Define 20 order info and mint to seller, and deal expected eth to buyer | |
OffererStructs.ERC20Order memory erc20Order = OffererStructs.ERC20Order({ | |
amount: 10 ** 18, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC20), | |
expiryLength: 30 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.principal | |
}) | |
}); | |
OfferItem memory offerItem = | |
OfferItem({itemType: ItemType.ERC20, token: erc20Order.info.tokenContract, identifierOrCriteria: 0, startAmount: erc20Order.amount, endAmount: erc20Order.amount}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC20, | |
token: erc20Order.info.tokenContract, | |
identifierOrCriteria: 0, | |
startAmount: erc20Order.amount, | |
endAmount: erc20Order.amount, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc20Order.info.rights, | |
signerSalt: erc20Order.info.signerSalt, | |
expiryLength: erc20Order.info.expiryLength, | |
expiryType: erc20Order.info.expiryType, | |
targetToken: erc20Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.3 ether; | |
vm.deal(buyer.addr, expectedETH); | |
mockERC20.mint(seller.addr, erc20Order.amount); | |
// Create order hash and id | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC20OrderHashAndId(seller.addr, address(conduit), erc20Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createSellerSignedOrder(seller, offerItem, createOrderHash, erc20Order.info.signerSalt, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createBuyerFillOrder(buyer, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller ERC20s => WrapReceipt, black line, order 0 offer item 0 matches with order 1 consideration item 0 | |
fulfillments[0] = _constructFulfillment(0, 0, 1, 0); | |
// Wrap Receipt => Seller, red line, order 1 offer item 0 matches with order 0 consideration item 1 | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 1); | |
// Buyer ETH => Seller, blue line, order 2 offer item 0 matches with order 0 consideration item 0 | |
// offer: (2, 0); consideration: (0, 0); (orderIndex, itemIndex) | |
fulfillments[2] = _constructFulfillment(2, 0, 0, 0); | |
// Match orders | |
vm.startPrank(buyer.addr); | |
_trackMatchOrderGasBefore(); | |
// seaport.fulfillAvailableAdvancedOrders() | |
seaport.matchAdvancedOrders{value: expectedETH}(orders, new CriteriaResolver[](0), fulfillments, buyer.addr); | |
_trackMatchOrderGasAfter(); | |
vm.stopPrank(); | |
// Check | |
assertEq(mockERC20.balanceOf(address(dt)), erc20Order.amount); | |
assertEq(seller.addr.balance, expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
assertEq(mockERC20.balanceOf(seller.addr), 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc20Order.info.expiryLength); | |
} | |
function test20OfferFilledBySeller() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC20.approve(address(conduit), type(uint256).max); | |
vm.prank(buyer.addr); | |
weth.approve(address(conduit), type(uint256).max); | |
// Define 20 order info and mint tokens seller, and mint expected weth to buyer | |
OffererStructs.ERC20Order memory erc20Order = OffererStructs.ERC20Order({ | |
amount: 10 ** 18, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC20), | |
expiryLength: block.timestamp + 40 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.delegate | |
}) | |
}); | |
OfferItem memory offerItem = | |
OfferItem({itemType: ItemType.ERC20, token: address(mockERC20), identifierOrCriteria: 0, startAmount: erc20Order.amount, endAmount: erc20Order.amount}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC20, | |
token: erc20Order.info.tokenContract, | |
identifierOrCriteria: 0, | |
startAmount: erc20Order.amount, | |
endAmount: erc20Order.amount, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc20Order.info.rights, | |
signerSalt: erc20Order.info.signerSalt, | |
expiryLength: erc20Order.info.expiryLength, | |
expiryType: erc20Order.info.expiryType, | |
targetToken: erc20Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.22 ether; | |
weth.mint(buyer.addr, expectedETH); | |
mockERC20.mint(seller.addr, erc20Order.amount); | |
// Create order hash | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC20OrderHashAndId(buyer.addr, address(conduit), erc20Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createBuyerSignedOrder(buyer, createOrderHash, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createSellerFillOrder(seller, offerItem, erc20Order.info.signerSalt, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller ERC20s => Liquid Delegate V2 | |
fulfillments[0] = _constructFulfillment(2, 0, 1, 0); | |
// Wrap Receipt => Buyer | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 0); | |
// Buyer ETH => Seller | |
fulfillments[2] = _constructFulfillment(0, 0, 2, 0); | |
// Match orders | |
vm.startPrank(seller.addr); | |
_trackMatchOrderGasBefore(); | |
seaport.matchAdvancedOrders(orders, new CriteriaResolver[](0), fulfillments, seller.addr); | |
_trackMatchOrderGasAfter(); | |
// Check | |
assertEq(mockERC20.balanceOf(address(dt)), erc20Order.amount); | |
assertEq(weth.balanceOf(seller.addr), expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
assertEq(mockERC20.balanceOf(seller.addr), 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc20Order.info.expiryLength); | |
} | |
function test1155OrderFilledByBuyer() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC1155.setApprovalForAll(address(conduit), true); | |
// Define 1155 order info and mint to seller, and deal expected eth to buyer | |
OffererStructs.ERC1155Order memory erc1155Order = OffererStructs.ERC1155Order({ | |
amount: 10 ** 18, | |
tokenId: 42, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC1155), | |
expiryLength: 30 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.principal | |
}) | |
}); | |
OfferItem memory offerItem = OfferItem({ | |
itemType: ItemType.ERC1155, | |
token: erc1155Order.info.tokenContract, | |
identifierOrCriteria: erc1155Order.tokenId, | |
startAmount: erc1155Order.amount, | |
endAmount: erc1155Order.amount | |
}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC1155, | |
token: erc1155Order.info.tokenContract, | |
identifierOrCriteria: erc1155Order.tokenId, | |
startAmount: erc1155Order.amount, | |
endAmount: erc1155Order.amount, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc1155Order.info.rights, | |
signerSalt: erc1155Order.info.signerSalt, | |
expiryLength: erc1155Order.info.expiryLength, | |
expiryType: erc1155Order.info.expiryType, | |
targetToken: erc1155Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.3 ether; | |
vm.deal(buyer.addr, expectedETH); | |
mockERC1155.mint(seller.addr, erc1155Order.tokenId, erc1155Order.amount, ""); | |
// Create order hash and id | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC1155OrderHashAndId(seller.addr, address(conduit), erc1155Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createSellerSignedOrder(seller, offerItem, createOrderHash, erc1155Order.info.signerSalt, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createBuyerFillOrder(buyer, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller ERC1155s => WrapReceipt, black line, order 0 offer item 0 matches with order 1 consideration item 0 | |
fulfillments[0] = _constructFulfillment(0, 0, 1, 0); | |
// Wrap Receipt => Seller, red line, order 1 offer item 0 matches with order 0 consideration item 1 | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 1); | |
// Buyer ETH => Seller, blue line, order 2 offer item 0 matches with order 0 consideration item 0 | |
// offer: (2, 0); consideration: (0, 0); (orderIndex, itemIndex) | |
fulfillments[2] = _constructFulfillment(2, 0, 0, 0); | |
// Match orders | |
vm.startPrank(buyer.addr); | |
_trackMatchOrderGasBefore(); | |
seaport.matchAdvancedOrders{value: expectedETH}(orders, new CriteriaResolver[](0), fulfillments, buyer.addr); | |
_trackMatchOrderGasAfter(); | |
vm.stopPrank(); | |
// Check | |
assertEq(mockERC1155.balanceOf(address(dt), erc1155Order.tokenId), erc1155Order.amount); | |
assertEq(seller.addr.balance, expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
assertEq(mockERC1155.balanceOf(seller.addr, erc1155Order.tokenId), 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc1155Order.info.expiryLength); | |
} | |
function test1155OfferFilledBySeller() public { | |
// Approve conduit for token for seller | |
vm.prank(seller.addr); | |
mockERC1155.setApprovalForAll(address(conduit), true); | |
vm.prank(buyer.addr); | |
weth.approve(address(conduit), type(uint256).max); | |
// Define 1155 order info and mint tokens seller, and mint expected weth to buyer | |
OffererStructs.ERC1155Order memory erc1155Order = OffererStructs.ERC1155Order({ | |
amount: 10 ** 18, | |
tokenId: 42, | |
info: OffererStructs.Order({ | |
rights: "", | |
tokenContract: address(mockERC1155), | |
expiryLength: block.timestamp + 40 days, | |
expiryType: OffererEnums.ExpiryType.relative, | |
signerSalt: 9, | |
targetToken: OffererEnums.TargetToken.delegate | |
}) | |
}); | |
OfferItem memory offerItem = OfferItem({ | |
itemType: ItemType.ERC1155, | |
token: erc1155Order.info.tokenContract, | |
identifierOrCriteria: erc1155Order.tokenId, | |
startAmount: erc1155Order.amount, | |
endAmount: erc1155Order.amount | |
}); | |
ConsiderationItem memory considerationItem = ConsiderationItem({ | |
itemType: ItemType.ERC1155, | |
token: erc1155Order.info.tokenContract, | |
identifierOrCriteria: erc1155Order.tokenId, | |
startAmount: erc1155Order.amount, | |
endAmount: erc1155Order.amount, | |
recipient: payable(address(createOfferer)) | |
}); | |
bytes memory extraData = abi.encode( | |
OffererStructs.Context({ | |
rights: erc1155Order.info.rights, | |
signerSalt: erc1155Order.info.signerSalt, | |
expiryLength: erc1155Order.info.expiryLength, | |
expiryType: erc1155Order.info.expiryType, | |
targetToken: erc1155Order.info.targetToken | |
}) | |
); | |
uint256 expectedETH = 0.22 ether; | |
weth.mint(buyer.addr, expectedETH); | |
mockERC1155.mint(seller.addr, erc1155Order.tokenId, erc1155Order.amount, ""); | |
// Create order hash | |
(uint256 createOrderHash, uint256 delegateId) = createOfferer.calculateERC1155OrderHashAndId(buyer.addr, address(conduit), erc1155Order); | |
// Build Order | |
AdvancedOrder[] memory orders = new AdvancedOrder[](3); | |
orders[0] = _createBuyerSignedOrder(buyer, createOrderHash, expectedETH); | |
orders[1] = _createContractOrder(considerationItem, createOrderHash, extraData); | |
orders[2] = _createSellerFillOrder(seller, offerItem, erc1155Order.info.signerSalt, expectedETH); | |
// ============== Set Fulfillments =============== | |
// Fulfillments tells Seaport how to match the different order components to ensure | |
// everyone's conditions are satisfied. | |
Fulfillment[] memory fulfillments = new Fulfillment[](3); | |
// Seller ERC1155s => Liquid Delegate V2 | |
fulfillments[0] = _constructFulfillment(2, 0, 1, 0); | |
// Wrap Receipt => Buyer | |
fulfillments[1] = _constructFulfillment(1, 0, 0, 0); | |
// Buyer ETH => Seller | |
fulfillments[2] = _constructFulfillment(0, 0, 2, 0); | |
// Match orders | |
vm.startPrank(seller.addr); | |
_trackMatchOrderGasBefore(); | |
seaport.matchAdvancedOrders(orders, new CriteriaResolver[](0), fulfillments, seller.addr); | |
_trackMatchOrderGasAfter(); | |
// Check | |
assertEq(mockERC1155.balanceOf(address(dt), erc1155Order.tokenId), erc1155Order.amount); | |
assertEq(weth.balanceOf(seller.addr), expectedETH); | |
assertEq(buyer.addr.balance, 0); | |
assertEq(mockERC1155.balanceOf(seller.addr, erc1155Order.tokenId), 0); | |
IDelegateTokenStructs.DelegateInfo memory delegateInfo = dt.getDelegateInfo(delegateId); | |
assertEq(dt.ownerOf(delegateId), buyer.addr); | |
assertEq(principal.ownerOf(delegateId), seller.addr); | |
assertEq(delegateInfo.expiry, block.timestamp + erc1155Order.info.expiryLength); | |
} | |
function _trackMatchOrderGasBefore() internal { | |
startGas = gasleft(); | |
} | |
function _trackMatchOrderGasAfter() internal view { | |
uint256 gasUsed = startGas - gasleft(); | |
console2.log("gas use by matchAdvancedOrder", gasUsed); | |
} | |
function _createSellerFillOrder(User memory user, OfferItem memory offerItem, uint256 signerSalt, uint256 expectedETH) internal view returns (AdvancedOrder memory) { | |
OfferItem[] memory offer = new OfferItem[](1); | |
offer[0] = offerItem; | |
ConsiderationItem[] memory consideration = new ConsiderationItem[](1); | |
consideration[0] = ConsiderationItem({ | |
itemType: ItemType.ERC20, | |
token: address(weth), | |
identifierOrCriteria: 0, | |
startAmount: expectedETH, | |
endAmount: expectedETH, | |
recipient: payable(user.addr) | |
}); | |
OrderParameters memory orderParams = OrderParameters({ | |
offerer: user.addr, | |
zone: address(0), | |
offer: offer, | |
consideration: consideration, | |
orderType: OrderType.FULL_OPEN, | |
startTime: 0, | |
endTime: block.timestamp + 3 days, | |
zoneHash: bytes32(0), | |
salt: signerSalt, | |
conduitKey: conduitKey, | |
totalOriginalConsiderationItems: 1 | |
}); | |
return AdvancedOrder({parameters: orderParams, numerator: 1, denominator: 1, signature: "", extraData: ""}); | |
} | |
function _createSellerSignedOrder(User memory user, OfferItem memory offerItem, uint256 createOrderHash, uint256 signerSalt, uint256 expectedETH) | |
internal | |
view | |
returns (AdvancedOrder memory) | |
{ | |
OfferItem[] memory offer = new OfferItem[](1); | |
offer[0] = offerItem; | |
ConsiderationItem[] memory consideration = new ConsiderationItem[](2); | |
consideration[0] = ConsiderationItem({ | |
itemType: ItemType.NATIVE, | |
token: address(0), | |
identifierOrCriteria: 0, | |
startAmount: expectedETH, | |
endAmount: expectedETH, | |
recipient: payable(user.addr) | |
}); | |
consideration[1] = ConsiderationItem({ | |
itemType: ItemType.ERC721, | |
token: address(createOfferer), | |
identifierOrCriteria: createOrderHash, | |
startAmount: 1, | |
endAmount: 1, | |
recipient: payable(user.addr) | |
}); | |
OrderParameters memory orderParams = OrderParameters({ | |
offerer: user.addr, | |
zone: address(0), | |
offer: offer, | |
consideration: consideration, | |
orderType: OrderType.FULL_OPEN, | |
startTime: 0, | |
endTime: block.timestamp + 3 days, | |
zoneHash: bytes32(0), | |
salt: signerSalt, | |
conduitKey: conduitKey, | |
totalOriginalConsiderationItems: 2 | |
}); | |
return AdvancedOrder({parameters: orderParams, numerator: 1, denominator: 1, signature: signOrder(seaport, user, orderParams), extraData: ""}); | |
} | |
function _createContractOrder(ConsiderationItem memory considerationItem, uint256 createOrderHash, bytes memory extraData) internal view returns (AdvancedOrder memory) { | |
OfferItem[] memory offer = new OfferItem[](1); | |
offer[0] = OfferItem({itemType: ItemType.ERC721, token: address(createOfferer), identifierOrCriteria: createOrderHash, startAmount: 1, endAmount: 1}); | |
ConsiderationItem[] memory consideration = new ConsiderationItem[](1); | |
consideration[0] = considerationItem; | |
OrderParameters memory orderParams = OrderParameters({ | |
offerer: address(createOfferer), | |
zone: address(0), | |
offer: offer, | |
consideration: consideration, | |
orderType: OrderType.CONTRACT, | |
startTime: 0, | |
endTime: block.timestamp + 3 days, | |
zoneHash: bytes32(0), | |
salt: 1, | |
conduitKey: conduitKey, | |
totalOriginalConsiderationItems: 1 | |
}); | |
SpentItem[] memory minimumReceived = new SpentItem[](1); | |
minimumReceived[0] = SpentItem({itemType: offer[0].itemType, token: offer[0].token, identifier: offer[0].identifierOrCriteria, amount: offer[0].endAmount}); | |
SpentItem[] memory maximumSpent = new SpentItem[](1); | |
maximumSpent[0] = | |
SpentItem({itemType: consideration[0].itemType, token: consideration[0].token, identifier: consideration[0].identifierOrCriteria, amount: consideration[0].endAmount}); | |
createOfferer.previewOrder(address(seaport), address(0), minimumReceived, maximumSpent, extraData); | |
return AdvancedOrder({parameters: orderParams, numerator: 1, denominator: 1, signature: "", extraData: extraData}); | |
} | |
function _createBuyerFillOrder(User memory user, uint256 expectedETH) internal view returns (AdvancedOrder memory) { | |
OfferItem[] memory offer = new OfferItem[](1); | |
offer[0] = OfferItem({itemType: ItemType.NATIVE, token: address(0), identifierOrCriteria: 0, startAmount: expectedETH, endAmount: expectedETH}); | |
ConsiderationItem[] memory consideration = new ConsiderationItem[](0); | |
OrderParameters memory orderParams = OrderParameters({ | |
offerer: user.addr, | |
zone: address(0), | |
offer: offer, | |
consideration: consideration, | |
orderType: OrderType.FULL_OPEN, | |
startTime: 0, | |
endTime: block.timestamp + 3 days, | |
zoneHash: bytes32(0), | |
salt: 1, | |
conduitKey: conduitKey, | |
totalOriginalConsiderationItems: 0 | |
}); | |
return AdvancedOrder({parameters: orderParams, numerator: 1, denominator: 1, signature: bytes(""), extraData: ""}); | |
} | |
function _createBuyerSignedOrder(User memory user, uint256 createOrderHash, uint256 expectedETH) internal view returns (AdvancedOrder memory) { | |
OfferItem[] memory offer = new OfferItem[](1); | |
offer[0] = OfferItem({itemType: ItemType.ERC20, token: address(weth), identifierOrCriteria: 0, startAmount: expectedETH, endAmount: expectedETH}); | |
ConsiderationItem[] memory consideration = new ConsiderationItem[](1); | |
consideration[0] = ConsiderationItem({ | |
itemType: ItemType.ERC721, | |
token: address(createOfferer), | |
identifierOrCriteria: createOrderHash, | |
startAmount: 1, | |
endAmount: 1, | |
recipient: payable(user.addr) | |
}); | |
OrderParameters memory orderParams = OrderParameters({ | |
offerer: user.addr, | |
zone: address(0), | |
offer: offer, | |
consideration: consideration, | |
orderType: OrderType.FULL_OPEN, | |
startTime: 0, | |
endTime: block.timestamp + 3 days, | |
zoneHash: bytes32(0), | |
salt: 1, | |
conduitKey: conduitKey, | |
totalOriginalConsiderationItems: 1 | |
}); | |
return AdvancedOrder({parameters: orderParams, numerator: 1, denominator: 1, signature: signOrder(seaport, user, orderParams), extraData: ""}); | |
} | |
function _constructFulfillment(uint256 _offerOrderIndex, uint256 _offerItemIndex, uint256 _considerationOrderIndex, uint256 _considerationItemIndex) | |
internal | |
pure | |
returns (Fulfillment memory) | |
{ | |
FulfillmentComponent[] memory offerComponents = new FulfillmentComponent[](1); | |
offerComponents[0] = FulfillmentComponent({orderIndex: _offerOrderIndex, itemIndex: _offerItemIndex}); | |
FulfillmentComponent[] memory considerationComponents = new FulfillmentComponent[](1); | |
considerationComponents[0] = FulfillmentComponent({orderIndex: _considerationOrderIndex, itemIndex: _considerationItemIndex}); | |
return Fulfillment({offerComponents: offerComponents, considerationComponents: considerationComponents}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
addition notes:
We want to prove that when fulfillAvailableAdvancedOrders is called,
even if a single contract order revert in generateOrder, the nonce of the contract on seaport side is still incremented
because ratifyOrder is never called back to the OfferCreaterer.sol, the nonce between OfferCreaterer.sol and Seaport nonce is out of sync
I do think, the very relevent test is already in the seaport repo
the javascript version of test from seaport repo is here:
https://github.com/ProjectOpenSea/seaport/blob/22ea29df3c241ebc17c95268164dde47e1186287/test/advanced.spec.ts#L3064
I think this test case is already show that partial revert does not revert the whole transaction
the foundry version of test from seaport repo
https://github.com/ProjectOpenSea/seaport/blob/22ea29df3c241ebc17c95268164dde47e1186287/test/foundry/offerers/BadOfferer.t.sol#L100
so such behavior (one single generate order failure does not revert transaction but still increment the nonce sliently) is tested and covered by seaport already