Skip to content

Commit e315dbe

Browse files
authored
BTT tests: English Auctions (#555)
* btt tests: createAuction * btt tests: bidInAuction * checkmark btt tests createAuction * btt tests: collectAuctionTokens * btt tests: collectAuctionPayout * btt tests: cancelAuction * btt tests: payout * btt tests: transferAuctionTokens * Update createAuction and bidInAuction tests * btt tests: _validateNewAuction * Add missing bidInAuction test * Add missing checkmarks --------- Signed-off-by: nkrishang <62195808+nkrishang@users.noreply.github.com>
1 parent 044cd26 commit e315dbe

File tree

16 files changed

+2821
-0
lines changed

16 files changed

+2821
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
// Test helper imports
5+
import "../../../utils/BaseTest.sol";
6+
7+
// Test contracts and interfaces
8+
import { RoyaltyPaymentsLogic } from "contracts/extension/plugin/RoyaltyPayments.sol";
9+
import { MarketplaceV3, IPlatformFee } from "contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol";
10+
import { EnglishAuctionsLogic } from "contracts/prebuilts/marketplace/english-auctions/EnglishAuctionsLogic.sol";
11+
import { TWProxy } from "contracts/infra/TWProxy.sol";
12+
import { ERC721Base } from "contracts/base/ERC721Base.sol";
13+
import { MockRoyaltyEngineV1 } from "../../../mocks/MockRoyaltyEngineV1.sol";
14+
import { PlatformFee } from "contracts/extension/PlatformFee.sol";
15+
16+
import { IEnglishAuctions } from "contracts/prebuilts/marketplace/IMarketplace.sol";
17+
18+
import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol";
19+
20+
contract ReentrantRecipient is ERC1155Holder {
21+
function onERC1155Received(
22+
address operator,
23+
address from,
24+
uint256 id,
25+
uint256 value,
26+
bytes memory data
27+
) public virtual override returns (bytes4) {
28+
uint256 auctionId = 0;
29+
uint256 bidAmount = 10 ether;
30+
EnglishAuctionsLogic(msg.sender).bidInAuction(auctionId, bidAmount);
31+
return super.onERC1155Received(operator, from, id, value, data);
32+
}
33+
}
34+
35+
contract EnglishAuctionsPayoutTest is BaseTest, IExtension {
36+
// Target contract
37+
address public marketplace;
38+
39+
// Participants
40+
address public marketplaceDeployer;
41+
address public seller;
42+
address public buyer;
43+
44+
// Auction parameters
45+
uint256 internal auctionId;
46+
uint256 internal bidAmount;
47+
address internal winningBidder = address(0x123);
48+
IEnglishAuctions.AuctionParameters internal auctionParams;
49+
50+
// Events
51+
event NewBid(
52+
uint256 indexed auctionId,
53+
address indexed bidder,
54+
address indexed assetContract,
55+
uint256 bidAmount,
56+
IEnglishAuctions.Auction auction
57+
);
58+
59+
function setUp() public override {
60+
super.setUp();
61+
62+
marketplaceDeployer = getActor(1);
63+
seller = getActor(2);
64+
buyer = getActor(3);
65+
66+
// Deploy implementation.
67+
Extension[] memory extensions = _setupExtensions();
68+
address impl = address(
69+
new MarketplaceV3(MarketplaceV3.MarketplaceConstructorParams(extensions, address(0), address(weth)))
70+
);
71+
72+
vm.prank(marketplaceDeployer);
73+
marketplace = address(
74+
new TWProxy(
75+
impl,
76+
abi.encodeCall(
77+
MarketplaceV3.initialize,
78+
(marketplaceDeployer, "", new address[](0), platformFeeRecipient, uint16(platformFeeBps))
79+
)
80+
)
81+
);
82+
83+
// Setup roles for seller and assets
84+
vm.startPrank(marketplaceDeployer);
85+
Permissions(marketplace).revokeRole(keccak256("ASSET_ROLE"), address(0));
86+
Permissions(marketplace).revokeRole(keccak256("LISTER_ROLE"), address(0));
87+
Permissions(marketplace).grantRole(keccak256("ASSET_ROLE"), address(erc1155));
88+
Permissions(marketplace).grantRole(keccak256("LISTER_ROLE"), seller);
89+
vm.stopPrank();
90+
91+
vm.label(impl, "MarketplaceV3_Impl");
92+
vm.label(marketplace, "Marketplace");
93+
vm.label(seller, "Seller");
94+
vm.label(buyer, "Buyer");
95+
vm.label(address(erc721), "ERC721_Token");
96+
vm.label(address(erc1155), "ERC1155_Token");
97+
98+
// Sample auction parameters.
99+
address assetContract = address(erc1155);
100+
uint256 tokenId = 0;
101+
uint256 quantity = 1;
102+
address currency = address(erc20);
103+
uint256 minimumBidAmount = 1 ether;
104+
uint256 buyoutBidAmount = 100 ether;
105+
uint64 timeBufferInSeconds = 10 seconds;
106+
uint64 bidBufferBps = 1000;
107+
uint64 startTimestamp = 100 minutes;
108+
uint64 endTimestamp = 200 minutes;
109+
110+
// Auction tokens.
111+
auctionParams = IEnglishAuctions.AuctionParameters(
112+
assetContract,
113+
tokenId,
114+
quantity,
115+
currency,
116+
minimumBidAmount,
117+
buyoutBidAmount,
118+
timeBufferInSeconds,
119+
bidBufferBps,
120+
startTimestamp,
121+
endTimestamp
122+
);
123+
124+
// Set bidAmount
125+
bidAmount = auctionParams.minimumBidAmount;
126+
127+
// Mint NFT to seller.
128+
erc721.mint(seller, 1); // to, amount
129+
erc1155.mint(seller, 0, 100); // to, id, amount
130+
131+
// Create auction
132+
vm.startPrank(seller);
133+
erc721.setApprovalForAll(marketplace, true);
134+
erc1155.setApprovalForAll(marketplace, true);
135+
auctionId = EnglishAuctionsLogic(marketplace).createAuction(auctionParams);
136+
vm.stopPrank();
137+
138+
// Mint currency to bidder.
139+
erc20.mint(buyer, 10_000 ether);
140+
141+
vm.prank(buyer);
142+
erc20.approve(marketplace, 100 ether);
143+
}
144+
145+
function _setupExtensions() internal returns (Extension[] memory extensions) {
146+
extensions = new Extension[](1);
147+
148+
// Deploy `EnglishAuctions`
149+
address englishAuctions = address(new EnglishAuctionsLogic(address(weth)));
150+
vm.label(englishAuctions, "EnglishAuctions_Extension");
151+
152+
// Extension: EnglishAuctionsLogic
153+
Extension memory extension_englishAuctions;
154+
extension_englishAuctions.metadata = ExtensionMetadata({
155+
name: "EnglishAuctionsLogic",
156+
metadataURI: "ipfs://EnglishAuctions",
157+
implementation: englishAuctions
158+
});
159+
160+
extension_englishAuctions.functions = new ExtensionFunction[](12);
161+
extension_englishAuctions.functions[0] = ExtensionFunction(
162+
EnglishAuctionsLogic.totalAuctions.selector,
163+
"totalAuctions()"
164+
);
165+
extension_englishAuctions.functions[1] = ExtensionFunction(
166+
EnglishAuctionsLogic.createAuction.selector,
167+
"createAuction((address,uint256,uint256,address,uint256,uint256,uint64,uint64,uint64,uint64))"
168+
);
169+
extension_englishAuctions.functions[2] = ExtensionFunction(
170+
EnglishAuctionsLogic.cancelAuction.selector,
171+
"cancelAuction(uint256)"
172+
);
173+
extension_englishAuctions.functions[3] = ExtensionFunction(
174+
EnglishAuctionsLogic.collectAuctionPayout.selector,
175+
"collectAuctionPayout(uint256)"
176+
);
177+
extension_englishAuctions.functions[4] = ExtensionFunction(
178+
EnglishAuctionsLogic.collectAuctionTokens.selector,
179+
"collectAuctionTokens(uint256)"
180+
);
181+
extension_englishAuctions.functions[5] = ExtensionFunction(
182+
EnglishAuctionsLogic.bidInAuction.selector,
183+
"bidInAuction(uint256,uint256)"
184+
);
185+
extension_englishAuctions.functions[6] = ExtensionFunction(
186+
EnglishAuctionsLogic.isNewWinningBid.selector,
187+
"isNewWinningBid(uint256,uint256)"
188+
);
189+
extension_englishAuctions.functions[7] = ExtensionFunction(
190+
EnglishAuctionsLogic.getAuction.selector,
191+
"getAuction(uint256)"
192+
);
193+
extension_englishAuctions.functions[8] = ExtensionFunction(
194+
EnglishAuctionsLogic.getAllAuctions.selector,
195+
"getAllAuctions(uint256,uint256)"
196+
);
197+
extension_englishAuctions.functions[9] = ExtensionFunction(
198+
EnglishAuctionsLogic.getAllValidAuctions.selector,
199+
"getAllValidAuctions(uint256,uint256)"
200+
);
201+
extension_englishAuctions.functions[10] = ExtensionFunction(
202+
EnglishAuctionsLogic.getWinningBid.selector,
203+
"getWinningBid(uint256)"
204+
);
205+
extension_englishAuctions.functions[11] = ExtensionFunction(
206+
EnglishAuctionsLogic.isAuctionExpired.selector,
207+
"isAuctionExpired(uint256)"
208+
);
209+
210+
extensions[0] = extension_englishAuctions;
211+
}
212+
213+
address payable[] internal mockRecipients;
214+
uint256[] internal mockAmounts;
215+
MockRoyaltyEngineV1 internal royaltyEngine;
216+
217+
function _setupRoyaltyEngine() private {
218+
mockRecipients.push(payable(address(0x12345)));
219+
mockRecipients.push(payable(address(0x56789)));
220+
221+
mockAmounts.push(10 ether);
222+
mockAmounts.push(15 ether);
223+
224+
royaltyEngine = new MockRoyaltyEngineV1(mockRecipients, mockAmounts);
225+
}
226+
227+
function test_payout_whenZeroRoyaltyRecipients() public {
228+
vm.warp(auctionParams.startTimestamp);
229+
vm.prank(buyer);
230+
EnglishAuctionsLogic(marketplace).bidInAuction(auctionId, auctionParams.buyoutBidAmount);
231+
232+
vm.prank(seller);
233+
EnglishAuctionsLogic(marketplace).collectAuctionPayout(auctionId);
234+
235+
uint256 totalPrice = auctionParams.buyoutBidAmount;
236+
237+
uint256 platformFees = (totalPrice * platformFeeBps) / 10_000;
238+
239+
{
240+
// Platform fee recipient receives correct amount
241+
assertBalERC20Eq(address(erc20), platformFeeRecipient, platformFees);
242+
243+
// Seller gets total price minus royalty amounts
244+
assertBalERC20Eq(address(erc20), seller, totalPrice - platformFees);
245+
}
246+
}
247+
248+
modifier whenNonZeroRoyaltyRecipients() {
249+
_setupRoyaltyEngine();
250+
251+
// Add RoyaltyEngine to marketplace
252+
vm.prank(marketplaceDeployer);
253+
RoyaltyPaymentsLogic(marketplace).setRoyaltyEngine(address(royaltyEngine));
254+
255+
_;
256+
}
257+
258+
function test_payout_whenInsufficientFundsToPayRoyaltyAfterPlatformFeePayout() public whenNonZeroRoyaltyRecipients {
259+
vm.prank(marketplaceDeployer);
260+
PlatformFee(marketplace).setPlatformFeeInfo(platformFeeRecipient, 9999); // 99.99% fees;
261+
262+
// Buy tokens from listing.
263+
vm.warp(auctionParams.startTimestamp);
264+
vm.prank(buyer);
265+
EnglishAuctionsLogic(marketplace).bidInAuction(auctionId, auctionParams.buyoutBidAmount);
266+
267+
vm.prank(seller);
268+
vm.expectRevert("fees exceed the price");
269+
EnglishAuctionsLogic(marketplace).collectAuctionPayout(auctionId);
270+
}
271+
272+
function test_payout_whenSufficientFundsToPayRoyaltyAfterPlatformFeePayout() public whenNonZeroRoyaltyRecipients {
273+
assertEq(RoyaltyPaymentsLogic(marketplace).getRoyaltyEngineAddress(), address(royaltyEngine));
274+
275+
vm.warp(auctionParams.startTimestamp);
276+
vm.prank(buyer);
277+
EnglishAuctionsLogic(marketplace).bidInAuction(auctionId, auctionParams.buyoutBidAmount);
278+
279+
vm.prank(seller);
280+
EnglishAuctionsLogic(marketplace).collectAuctionPayout(auctionId);
281+
282+
uint256 totalPrice = auctionParams.buyoutBidAmount;
283+
uint256 platformFees = (totalPrice * platformFeeBps) / 10_000;
284+
285+
{
286+
// Royalty recipients receive correct amounts
287+
assertBalERC20Eq(address(erc20), mockRecipients[0], mockAmounts[0]);
288+
assertBalERC20Eq(address(erc20), mockRecipients[1], mockAmounts[1]);
289+
290+
// Platform fee recipient receives correct amount
291+
assertBalERC20Eq(address(erc20), platformFeeRecipient, platformFees);
292+
293+
// Seller gets total price minus royalty amounts
294+
assertBalERC20Eq(address(erc20), seller, totalPrice - mockAmounts[0] - mockAmounts[1] - platformFees);
295+
}
296+
}
297+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
function _payout(
2+
address _payer,
3+
address _payee,
4+
address _currencyToUse,
5+
uint256 _totalPayoutAmount,
6+
Auction memory _targetAuction
7+
)
8+
├── when there are zero royalty recipients ✅
9+
│ ├── it should transfer platform fee from payer to platform fee recipient
10+
│ └── it should transfer remainder of currency from payer to payee
11+
└── when there are non-zero royalty recipients
12+
├── when the total royalty payout exceeds remainder payout after having paid platform fee
13+
│ └── it should revert ✅
14+
└── when the total royalty payout does not exceed remainder payout after having paid platform fee ✅
15+
├── it should transfer platform fee from payer to platform fee recipient
16+
├── it should transfer royalty fee from payer to royalty recipients
17+
└── it should transfer remainder of currency from payer to payeew

0 commit comments

Comments
 (0)