Skip to content

Commit

Permalink
add transfer validator to v1 (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio authored Mar 29, 2024
1 parent 56c6115 commit d1e305c
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 13 deletions.
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

0 comments on commit d1e305c

Please sign in to comment.