Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add transfer validator to v1 #115

Merged
merged 2 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/.solhintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ src/clones
src/test/
src/shim/

src/interfaces/ITransferValidator.sol

test/
lib/
3 changes: 3 additions & 0 deletions src-upgradeable/src/ERC721ContractMetadataStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ library ERC721ContractMetadataStorage {
/// @notice Track the royalty info: address to receive royalties, and
/// royalty basis points.
ISeaDropTokenContractMetadataUpgradeable.RoyaltyInfo _royaltyInfo;
/// @notice Track the transfer validator.
/// The null address means no transfer validator is set.
address _transferValidator;
}

bytes32 internal constant STORAGE_SLOT =
Expand Down
54 changes: 43 additions & 11 deletions src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import {
} from "./interfaces/ISeaDropTokenContractMetadataUpgradeable.sol";

import {
ERC721AUpgradeable
} from "../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol";
ERC721AConduitPreapprovedUpgradeable
} from "./lib/ERC721AConduitPreapprovedUpgradeable.sol";

import {
ERC721TransferValidatorUpgradeable
} from "./lib/ERC721TransferValidatorUpgradeable.sol";

import { ERC721AUpgradeable } from "../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol";

import {
TwoStepOwnableUpgradeable
Expand All @@ -34,7 +40,7 @@ import {
* with additional metadata and ownership capabilities.
*/
contract ERC721ContractMetadataUpgradeable is
ERC721AUpgradeable,
ERC721AConduitPreapprovedUpgradeable,
TwoStepOwnableUpgradeable,
ISeaDropTokenContractMetadataUpgradeable
{
Expand All @@ -61,17 +67,11 @@ contract ERC721ContractMetadataUpgradeable is
string memory name,
string memory symbol
) internal onlyInitializing {
__ERC721A_init_unchained(name, symbol);
__ERC721AConduitPreapprovedUpgradeable_init_unchained(name, symbol);
__ConstructorInitializable_init_unchained();
__TwoStepOwnable_init_unchained();
__ERC721ContractMetadata_init_unchained(name, symbol);
}

function __ERC721ContractMetadata_init_unchained(
string memory,
string memory
) internal onlyInitializing {}

/**
* @notice Sets the base URI for the token metadata and emits an event.
*
Expand Down Expand Up @@ -270,7 +270,8 @@ contract ERC721ContractMetadataUpgradeable is
* @return royaltyAmount The royalty payment amount for _salePrice.
*/
function royaltyInfo(
uint256, /* _tokenId */
uint256,
/* _tokenId */
uint256 _salePrice
) external view returns (address receiver, uint256 royaltyAmount) {
// Put the royalty info on the stack for more efficient access.
Expand All @@ -286,6 +287,37 @@ contract ERC721ContractMetadataUpgradeable is
receiver = info.royaltyAddress;
}

/**
* @notice Set the transfer validator. Only callable by the token owner.
*/
function setTransferValidator(address newValidator) external onlyOwner {
// Set the new transfer validator.
_setTransferValidator(newValidator);
}

/**
* @dev Hook that is called before any token transfer.
* This includes minting and burning.
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 /* quantity */
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Call the transfer validator if one is set.
if (_transferValidator != address(0)) {
ITransferValidator(_transferValidator).validateTransfer(
msg.sender,
from,
to,
startTokenId
);
}
}
}

/**
* @notice Returns whether the interface is supported.
*
Expand Down
21 changes: 21 additions & 0 deletions src-upgradeable/src/interfaces/ITransferValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface ITransferValidator {
/// @notice Ensure that a transfer has been authorized for a specific tokenId
function validateTransfer(
address caller,
address from,
address to,
uint256 tokenId
) external view;

/// @notice Ensure that a transfer has been authorized for a specific amount of a specific tokenId, and reduce the transferable amount remaining
function validateTransfer(
address caller,
address from,
address to,
uint256 tokenId,
uint256 amount
) external;
}
33 changes: 33 additions & 0 deletions src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import { ERC721AUpgradeable } from "../../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol";

/**
* @title ERC721AConduitPreapproved
* @notice ERC721A with the OpenSea conduit preapproved.
*/
abstract contract ERC721AConduitPreapprovedUpgradeable is ERC721AUpgradeable {
/// @dev The canonical OpenSea conduit.
address internal constant _CONDUIT = 0x1E0049783F008A0085193E00003D00cd54003c71;

/**
* @notice Deploy the token contract with its name and symbol.
*/
function __ERC721AConduitPreapprovedUpgradeable_init_unchained(
string memory name, string memory symbol
) internal onlyInitializing {
__ERC721A_init_unchained(name, symbol);
}

/**
* @dev Returns if the `operator` is allowed to manage all of the
* assets of `owner`. Always returns true for the conduit.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
if (operator == _CONDUIT) {
return true;
}
return ERC721A.isApprovedForAll(owner, operator);
}
}
35 changes: 35 additions & 0 deletions src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {ERC721ContractMetadataStorage} from "../ERC721ContractMetadataStorage.sol";

/**
* @title ERC721TransferValidatorUpgradeable
* @notice Functionality to use a transfer validator.
*/
contract ERC721TransferValidatorUpgradeable {
using ERC721ContractMetadataStorage for ERC721ContractMetadataStorage.Layout;

/// @notice Emit an event when the transfer validator is updated.
event TransferValidatorUpdated(address oldValidator, address newValidator);

/// @notice Revert with an error if the transfer validator is being set to the same address.
error SameTransferValidator();

/// @notice Returns the currently active transfer validator.
/// The null address means no transfer validator is set.
function getTransferValidator() external view returns (address) {
return ERC721ContractMetadataStorage.layout()._transferValidator;
}

/// @notice Set the transfer validator.
/// The external method that uses this must include access control.
function _setTransferValidator(address newValidator) internal {
address oldValidator = ERC721ContractMetadataStorage.layout()._transferValidator;
if (oldValidator == newValidator) {
revert SameTransferValidator();
}
ERC721ContractMetadataStorage.layout()._transferValidator = newValidator;
emit TransferValidatorUpdated(oldValidator, newValidator);
}
}
39 changes: 38 additions & 1 deletion src/ERC721ContractMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { ERC721A } from "ERC721A/ERC721A.sol";

import { ERC721AConduitPreapproved } from "./lib/ERC721AConduitPreapproved.sol";

import { ERC721TransferValidator } from "./lib/ERC721TransferValidator.sol";

import { ITransferValidator } from "./interfaces/ITransferValidator.sol";

import { TwoStepOwnable } from "utility-contracts/TwoStepOwnable.sol";

import { IERC2981 } from "openzeppelin-contracts/interfaces/IERC2981.sol";
Expand All @@ -27,6 +31,7 @@ import {
*/
contract ERC721ContractMetadata is
ERC721AConduitPreapproved,
ERC721TransferValidator,
TwoStepOwnable,
ISeaDropTokenContractMetadata
{
Expand Down Expand Up @@ -261,7 +266,8 @@ contract ERC721ContractMetadata is
* @return royaltyAmount The royalty payment amount for _salePrice.
*/
function royaltyInfo(
uint256, /* _tokenId */
uint256,
/* _tokenId */
uint256 _salePrice
) external view returns (address receiver, uint256 royaltyAmount) {
// Put the royalty info on the stack for more efficient access.
Expand All @@ -275,6 +281,37 @@ contract ERC721ContractMetadata is
receiver = info.royaltyAddress;
}

/**
* @notice Set the transfer validator. Only callable by the token owner.
*/
function setTransferValidator(address newValidator) external onlyOwner {
// Set the new transfer validator.
_setTransferValidator(newValidator);
}

/**
* @dev Hook that is called before any token transfer.
* This includes minting and burning.
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 /* quantity */
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Call the transfer validator if one is set.
if (_transferValidator != address(0)) {
ITransferValidator(_transferValidator).validateTransfer(
msg.sender,
from,
to,
startTokenId
);
}
}
}

/**
* @notice Returns whether the interface is supported.
*
Expand Down
31 changes: 31 additions & 0 deletions src/clones/ERC721AConduitPreapprovedCloneable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import { ERC721ACloneable } from "./ERC721ACloneable.sol";

/**
* @title ERC721AConduitPreapprovedCloneable
* @notice ERC721A with the OpenSea conduit preapproved.
*/
abstract contract ERC721AConduitPreapprovedCloneable is ERC721ACloneable {
/// @dev The canonical OpenSea conduit.
address internal constant _CONDUIT =
0x1E0049783F008A0085193E00003D00cd54003c71;

/**
* @dev Returns if the `operator` is allowed to manage all of the
* assets of `owner`. Always returns true for the conduit.
*/
function isApprovedForAll(address owner, address operator)
public
view
virtual
override
returns (bool)
{
if (operator == _CONDUIT) {
return true;
}
return ERC721ACloneable.isApprovedForAll(owner, operator);
}
}
42 changes: 41 additions & 1 deletion src/clones/ERC721ContractMetadataCloneable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ import {
ISeaDropTokenContractMetadata
} from "../interfaces/ISeaDropTokenContractMetadata.sol";

import {
ERC721AConduitPreapprovedCloneable
} from "./ERC721AConduitPreapprovedCloneable.sol";

import { ERC721ACloneable } from "./ERC721ACloneable.sol";

import { ERC721TransferValidator } from "../lib/ERC721TransferValidator.sol";

import { ITransferValidator } from "../interfaces/ITransferValidator.sol";

import { TwoStepOwnable } from "utility-contracts/TwoStepOwnable.sol";

import { IERC2981 } from "openzeppelin-contracts/interfaces/IERC2981.sol";
Expand All @@ -24,7 +32,8 @@ import {
* with additional metadata and ownership capabilities.
*/
contract ERC721ContractMetadataCloneable is
ERC721ACloneable,
ERC721AConduitPreapprovedCloneable,
ERC721TransferValidator,
TwoStepOwnable,
ISeaDropTokenContractMetadata
{
Expand Down Expand Up @@ -267,6 +276,37 @@ contract ERC721ContractMetadataCloneable is
receiver = info.royaltyAddress;
}

/**
* @notice Set the transfer validator. Only callable by the token owner.
*/
function setTransferValidator(address newValidator) external onlyOwner {
// Set the new transfer validator.
_setTransferValidator(newValidator);
}

/**
* @dev Hook that is called before any token transfer.
* This includes minting and burning.
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 /* quantity */
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Call the transfer validator if one is set.
if (_transferValidator != address(0)) {
ITransferValidator(_transferValidator).validateTransfer(
msg.sender,
from,
to,
startTokenId
);
}
}
}

/**
* @notice Returns whether the interface is supported.
*
Expand Down
22 changes: 22 additions & 0 deletions src/interfaces/ITransferValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

interface ITransferValidator {
/// @notice Ensure that a transfer has been authorized for a specific tokenId.
function validateTransfer(
address caller,
address from,
address to,
uint256 tokenId
) external view;

/// @notice Ensure that a transfer has been authorized for a specific amount of
// a specific tokenId, and reduce the transferable amount remaining.
function validateTransfer(
address caller,
address from,
address to,
uint256 tokenId,
uint256 amount
) external;
}
Loading
Loading