Skip to content

Commit 27d0e57

Browse files
kumaryash90Krishang NadgaudaKrishang Nadgaudankrishang
authored
Marketplace V3 + Latest Plugin Pattern (#308)
* first pass at extension interfaces * forge updates * DirectListings extension WIP * english auction * wip Offers.sol * getOffers * getOffers update * update DirectListings * update interfaces * optimize * optimize * Format DirectListings in library storage code pattern * DirectListings timestamps * Offers: library storage pattern * EnglishAuctions: library storage pattern * marketplace implementation * forge update * WIP Marketplace entrypoint * Inherit relevant extensions in entrypoint * Add fallback logic to entrypoint * rename extensions -> extension * Write funcSig -> ext address Map contract * Fix compilation errors * run prettier * remove unused Context * Cleanup extensions inherited by entrypoint * replace IContext with ERC2771ContextConsumer * Fix typos lisiting -> listing * use platform fee extension in DirectListings * add totalListings to IDirectListings * Create DirectListings test * Add totalAuctions view fn to EnglishAuctions * remove redundant native token from Offers * remove PlatformFee from marketplace extensions storage * Marketplace tests scaffolding * DirectListings: add createListing tests * DirectListings: add updateListing tests * DirectListings: add cancelListing tests * Use modifier in approveCurrencyForListing * DirectListings: add approveBuyerForListing tests * DirectListings: add view fns for mappings in state * DirectListings: add approveCurrencyForListing tests * DirectListings fix: provide expected currency and total price in buyFromListing * DirectListings fix: startTimestamps are inclusive * DirectListings: add buyFromListing tests * cannot fuzz view fns quick enough * EnglishAuctions: add createAuction tests * EnglishAuctions: cancel auctions only when no bids * EnglishAuctions: correct timestamps * EnglishAuctions: add tests for cancelAuction * EnglishAuctions: add tests for bidInAuction * EnglishAuctions: add tests for collectAuctionPayout and collectAuctionTokens * EnglishAuctions: tests for view functions * Offers: tests for makeOffer * Offers: tests for cancelOffer * Offers: change payout to currencyTransferWithWrapper * Offers: tests for acceptOffer * correct timestamps * Offers: tests for view functions * remove regular implementations of extensions * Update require statement * Give startTimestamp a 1 hour buffer * Cleanup: createListing related logic * cleanup require statments * update comments for updateListing * cleanup DirectListings * Cleanup directlistings file * Add rest of the code comments to IMarketplace * remove updateAuction * Cleanup EnglishAuctions * DirectListings: use onlyExistingListing modifier in getListing * consistent behavior of getAll.. functions * wip cleanup Offers * cleanup offers * Fix DirectListings tests * Fix EnglighAuctions test * Fix Offers tests * re-organize marketplace files * docs update * docs update * pkg bump * pkg release * Fix: (C-1) Marketplace funds can be drained due to lack of auction payout accounting * Fix: (C-4) Auction token collection not tracked enables stealing ERC-1155 tokens * Already fixed: (C-3) Auction creator collecting payout causes buyer to lose their purchased tokens * Fix: (C-2) Unsafe listing update allows seller front-run to buyer purchase * patch tests * update forge * (M-1) setExtension allows selector clashes enables incorrect select→extension mapping * (L-1), (L-2) - fix timestamps * (L-3) Buyer can overpay when using buy out option * (L-4) Off-chain application might block itself from buying listing * (Q-1) _validateOwnershipAndApproval is not needed for creating auctions * (Q-2) Unused state variable * Nitpicks: Validate bid amounts inside _validateNewAuction() * docs * v3.3.0-1 * v3.3.0-2 * revised fix: (L-4) Off-chain application might block itself from buying listing * test for fix: (M-1) setExtension allows selector clashes enables incorrect select→extension mapping * update version * setup lister and asset roles in initialize * update marketplace-v3 with latest plugin pattern * docs * fix getters * v3.3.1-0 * change contract-type to MarketplaceRouter * v3.3.1-1 * forge update * fix getters * v3.3.1-2 * add a status flag, remove deletion * update tests, fix bugs * v3.3.1-3 * update event params * rename status DNE -> UNSET * docs * v3.3.3-0 * update events * rename to MarketplaceV3 --------- Co-authored-by: Krishang Nadgauda <[email protected]> Co-authored-by: Krishang Nadgauda <[email protected]> Co-authored-by: Krishang <[email protected]>
1 parent 107a227 commit 27d0e57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+10394
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./ContractMetadataStorage.sol";
5+
import "../../extension/interface/IContractMetadata.sol";
6+
7+
/**
8+
* @title Contract Metadata
9+
* @notice Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
10+
* for you contract.
11+
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
12+
*/
13+
14+
abstract contract ContractMetadataLogic is IContractMetadata {
15+
/// @dev Returns the metadata URI of the contract.
16+
function contractURI() public view returns (string memory) {
17+
ContractMetadataStorage.Data storage data = ContractMetadataStorage.contractMetadataStorage();
18+
return data.contractURI;
19+
}
20+
21+
/**
22+
* @notice Lets a contract admin set the URI for contract-level metadata.
23+
* @dev Caller should be authorized to setup contractURI, e.g. contract admin.
24+
* See {_canSetContractURI}.
25+
* Emits {ContractURIUpdated Event}.
26+
*
27+
* @param _uri keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
28+
*/
29+
function setContractURI(string memory _uri) external override {
30+
if (!_canSetContractURI()) {
31+
revert("Not authorized");
32+
}
33+
34+
_setupContractURI(_uri);
35+
}
36+
37+
/// @dev Lets a contract admin set the URI for contract-level metadata.
38+
function _setupContractURI(string memory _uri) internal {
39+
ContractMetadataStorage.Data storage data = ContractMetadataStorage.contractMetadataStorage();
40+
string memory prevURI = data.contractURI;
41+
data.contractURI = _uri;
42+
43+
emit ContractURIUpdated(prevURI, _uri);
44+
}
45+
46+
/// @dev Returns whether contract metadata can be set in the given execution context.
47+
function _canSetContractURI() internal view virtual returns (bool);
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
library ContractMetadataStorage {
5+
bytes32 public constant CONTRACT_METADATA_STORAGE_POSITION = keccak256("contract.metadata.storage");
6+
7+
struct Data {
8+
string contractURI;
9+
}
10+
11+
function contractMetadataStorage() internal pure returns (Data storage contractMetadataData) {
12+
bytes32 position = CONTRACT_METADATA_STORAGE_POSITION;
13+
assembly {
14+
contractMetadataData.slot := position
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./ERC2771ContextStorage.sol";
5+
6+
/**
7+
* @dev Context variant with ERC2771 support.
8+
*/
9+
abstract contract ERC2771ContextUpgradeableLogic {
10+
function __ERC2771Context_init(address[] memory trustedForwarder) internal {
11+
__ERC2771Context_init_unchained(trustedForwarder);
12+
}
13+
14+
function __ERC2771Context_init_unchained(address[] memory trustedForwarder) internal {
15+
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();
16+
17+
for (uint256 i = 0; i < trustedForwarder.length; i++) {
18+
data._trustedForwarder[trustedForwarder[i]] = true;
19+
}
20+
}
21+
22+
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
23+
ERC2771ContextStorage.Data storage data = ERC2771ContextStorage.erc2771ContextStorage();
24+
return data._trustedForwarder[forwarder];
25+
}
26+
27+
function _msgSender() internal view virtual returns (address sender) {
28+
if (isTrustedForwarder(msg.sender)) {
29+
// The assembly code is more direct than the Solidity version using `abi.decode`.
30+
assembly {
31+
sender := shr(96, calldataload(sub(calldatasize(), 20)))
32+
}
33+
} else {
34+
return msg.sender;
35+
}
36+
}
37+
38+
function _msgData() internal view virtual returns (bytes calldata) {
39+
if (isTrustedForwarder(msg.sender)) {
40+
return msg.data[:msg.data.length - 20];
41+
} else {
42+
return msg.data;
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
library ERC2771ContextUpgradeableStorage {
5+
bytes32 public constant ERC2771_CONTEXT_UPGRADEABLE_STORAGE_POSITION =
6+
keccak256("erc2771.context.upgradeable.storage");
7+
8+
struct Data {
9+
mapping(address => bool) _trustedForwarder;
10+
}
11+
12+
function erc2771ContextUpgradeableStorage() internal pure returns (Data storage erc2771ContextData) {
13+
bytes32 position = ERC2771_CONTEXT_UPGRADEABLE_STORAGE_POSITION;
14+
assembly {
15+
erc2771ContextData.slot := position
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./PlatformFeeStorage.sol";
5+
import "../../extension/interface/IPlatformFee.sol";
6+
7+
/**
8+
* @title Platform Fee
9+
* @notice Thirdweb's `PlatformFee` is a contract extension to be used with any base contract. It exposes functions for setting and reading
10+
* the recipient of platform fee and the platform fee basis points, and lets the inheriting contract perform conditional logic
11+
* that uses information about platform fees, if desired.
12+
*/
13+
14+
abstract contract PlatformFeeLogic is IPlatformFee {
15+
/// @dev Returns the platform fee recipient and bps.
16+
function getPlatformFeeInfo() public view override returns (address, uint16) {
17+
PlatformFeeStorage.Data storage data = PlatformFeeStorage.platformFeeStorage();
18+
return (data.platformFeeRecipient, uint16(data.platformFeeBps));
19+
}
20+
21+
/**
22+
* @notice Updates the platform fee recipient and bps.
23+
* @dev Caller should be authorized to set platform fee info.
24+
* See {_canSetPlatformFeeInfo}.
25+
* Emits {PlatformFeeInfoUpdated Event}; See {_setupPlatformFeeInfo}.
26+
*
27+
* @param _platformFeeRecipient Address to be set as new platformFeeRecipient.
28+
* @param _platformFeeBps Updated platformFeeBps.
29+
*/
30+
function setPlatformFeeInfo(address _platformFeeRecipient, uint256 _platformFeeBps) external override {
31+
if (!_canSetPlatformFeeInfo()) {
32+
revert("Not authorized");
33+
}
34+
_setupPlatformFeeInfo(_platformFeeRecipient, _platformFeeBps);
35+
}
36+
37+
/// @dev Lets a contract admin update the platform fee recipient and bps
38+
function _setupPlatformFeeInfo(address _platformFeeRecipient, uint256 _platformFeeBps) internal {
39+
PlatformFeeStorage.Data storage data = PlatformFeeStorage.platformFeeStorage();
40+
if (_platformFeeBps > 10_000) {
41+
revert("Exceeds max bps");
42+
}
43+
44+
data.platformFeeBps = uint16(_platformFeeBps);
45+
data.platformFeeRecipient = _platformFeeRecipient;
46+
47+
emit PlatformFeeInfoUpdated(_platformFeeRecipient, _platformFeeBps);
48+
}
49+
50+
/// @dev Returns whether platform fee info can be set in the given execution context.
51+
function _canSetPlatformFeeInfo() internal view virtual returns (bool);
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
library PlatformFeeStorage {
5+
bytes32 public constant PLATFORM_FEE_STORAGE_POSITION = keccak256("platform.fee.storage");
6+
7+
struct Data {
8+
/// @dev The address that receives all platform fees from all sales.
9+
address platformFeeRecipient;
10+
/// @dev The % of primary sales collected as platform fees.
11+
uint16 platformFeeBps;
12+
}
13+
14+
function platformFeeStorage() internal pure returns (Data storage platformFeeData) {
15+
bytes32 position = PLATFORM_FEE_STORAGE_POSITION;
16+
assembly {
17+
platformFeeData.slot := position
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
3+
pragma solidity ^0.8.0;
4+
5+
import "./ReentrancyGuardStorage.sol";
6+
7+
/**
8+
* @dev Contract module that helps prevent reentrant calls to a function.
9+
*
10+
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
11+
* available, which can be applied to functions to make sure there are no nested
12+
* (reentrant) calls to them.
13+
*
14+
* Note that because there is a single `nonReentrant` guard, functions marked as
15+
* `nonReentrant` may not call one another. This can be worked around by making
16+
* those functions `private`, and then adding `external` `nonReentrant` entry
17+
* points to them.
18+
*
19+
* TIP: If you would like to learn more about reentrancy and alternative ways
20+
* to protect against it, check out our blog post
21+
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
22+
*/
23+
abstract contract ReentrancyGuardLogic {
24+
// Booleans are more expensive than uint256 or any type that takes up a full
25+
// word because each write operation emits an extra SLOAD to first read the
26+
// slot's contents, replace the bits taken up by the boolean, and then write
27+
// back. This is the compiler's defense against contract upgrades and
28+
// pointer aliasing, and it cannot be disabled.
29+
30+
// The values being non-zero value makes deployment a bit more expensive,
31+
// but in exchange the refund on every call to nonReentrant will be lower in
32+
// amount. Since refunds are capped to a percentage of the total
33+
// transaction's gas, it is best to keep them low in cases like this one, to
34+
// increase the likelihood of the full refund coming into effect.
35+
uint256 private constant _NOT_ENTERED = 1;
36+
uint256 private constant _ENTERED = 2;
37+
38+
function __ReentrancyGuard_init() internal {
39+
__ReentrancyGuard_init_unchained();
40+
}
41+
42+
function __ReentrancyGuard_init_unchained() internal {
43+
ReentrancyGuardStorage.Data storage data = ReentrancyGuardStorage.reentrancyGuardStorage();
44+
data._status = _NOT_ENTERED;
45+
}
46+
47+
/**
48+
* @dev Prevents a contract from calling itself, directly or indirectly.
49+
* Calling a `nonReentrant` function from another `nonReentrant`
50+
* function is not supported. It is possible to prevent this from happening
51+
* by making the `nonReentrant` function external, and making it call a
52+
* `private` function that does the actual work.
53+
*/
54+
modifier nonReentrant() {
55+
ReentrancyGuardStorage.Data storage data = ReentrancyGuardStorage.reentrancyGuardStorage();
56+
// On the first call to nonReentrant, _notEntered will be true
57+
require(data._status != _ENTERED, "ReentrancyGuard: reentrant call");
58+
59+
// Any calls to nonReentrant after this point will fail
60+
data._status = _ENTERED;
61+
62+
_;
63+
64+
// By storing the original value once again, a refund is triggered (see
65+
// https://eips.ethereum.org/EIPS/eip-2200)
66+
data._status = _NOT_ENTERED;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
library ReentrancyGuardStorage {
5+
bytes32 public constant REENTRANCY_GUARD_STORAGE_POSITION = keccak256("reentrancy.guard.storage");
6+
7+
struct Data {
8+
uint256 _status;
9+
}
10+
11+
function reentrancyGuardStorage() internal pure returns (Data storage reentrancyGuardData) {
12+
bytes32 position = REENTRANCY_GUARD_STORAGE_POSITION;
13+
assembly {
14+
reentrancyGuardData.slot := position
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)