Skip to content

Instantly share code, notes, and snippets.

@JeffCX
Created October 3, 2023 13:21
Show Gist options
  • Save JeffCX/2d91b4f5f781f08a249350e748d85131 to your computer and use it in GitHub Desktop.
Save JeffCX/2d91b4f5f781f08a249350e748d85131 to your computer and use it in GitHub Desktop.
CreateOfferer.t.sol
// 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});
}
}
@JeffCX
Copy link
Author

JeffCX commented Oct 3, 2023

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

    function testOrderRevert() public {
        uint256 id = uint256(BadOfferer.Path.REVERT);
        test(
            this.execOrderWithContext,
            Context({
                seaport: consideration,
                id: id,
                eoa: false,
                // shouldn't fail because the revert happens within
                // GenerateOrder, so it can be safely skipped
                shouldFail: false
            })
        );
        test(
            this.execOrderWithContext,
            Context({
                seaport: referenceConsideration,
                id: id,
                eoa: false,
                shouldFail: false
            })
        );
    }

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment