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 v2 #116

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
4 changes: 3 additions & 1 deletion config/.solhintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ node_modules/
src/clones
src/test/
src/shim/
src/interfaces/IDelegationRegistry.sol
src/lib/ERC1155.sol

src/interfaces/IDelegationRegistry.sol
src/interfaces/ITransferValidator.sol

test/
lib/
37 changes: 37 additions & 0 deletions src/clones/ERC1155ContractMetadataCloneable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
ERC1155ConduitPreapproved
} from "../lib/ERC1155ConduitPreapproved.sol";

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

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

import { ERC1155 } from "solady/src/tokens/ERC1155.sol";

import { ERC2981 } from "solady/src/tokens/ERC2981.sol";
Expand All @@ -30,6 +34,7 @@ import {
*/
contract ERC1155ContractMetadataCloneable is
ERC1155ConduitPreapproved,
TokenTransferValidator,
ERC2981,
Ownable,
IERC1155ContractMetadata,
Expand Down Expand Up @@ -306,6 +311,38 @@ contract ERC1155ContractMetadataCloneable is
return _baseURI;
}

/// @dev Override this function to return true if `_beforeTokenTransfer` is used.
function _useBeforeTokenTransfer() internal view virtual override returns (bool) {
return true;
}

/**
* @dev Hook that is called before any token transfer.
* This includes minting and burning.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory /* data */
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Call the transfer validator if one is set.
if (_transferValidator != address(0)) {
for (uint256 i = 0; i < ids.length; i++) {
ITransferValidator(_transferValidator).validateTransfer(
msg.sender,
from,
to,
ids[i],
amounts[i]
);
}
}
}
}

/**
* @notice Returns whether the interface is supported.
*
Expand Down
36 changes: 0 additions & 36 deletions src/clones/ERC1155SeaDropCloneable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,6 @@ contract ERC1155SeaDropCloneable is ERC1155SeaDropContractOffererCloneable {
);
}

/**
* @dev Auto-approve the conduit after mint or transfer.
*
* @custom:param from The address to transfer from.
* @param to The address to transfer to.
* @custom:param ids The token ids to transfer.
* @custom:param amounts The quantities to transfer.
* @custom:param data The data to pass if receiver is a contract.
*/
function _afterTokenTransfer(
address /* from */,
address to,
uint256[] memory /* ids */,
uint256[] memory /* amounts */,
bytes memory /* data */
) internal virtual override {
// Auto-approve the conduit.
if (to != address(0) && !isApprovedForAll(to, _CONDUIT)) {
_setApprovalForAll(to, _CONDUIT, true);
}
}

/**
* @dev Override this function to return true if `_afterTokenTransfer` is
* used. The is to help the compiler avoid producing dead bytecode.
*/
function _useAfterTokenTransfer()
internal
view
virtual
override
returns (bool)
{
return true;
}

/**
* @notice Burns a token, restricted to the owner or approved operator,
* and must have sufficient balance.
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;
}
45 changes: 45 additions & 0 deletions src/lib/ERC1155ContractMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
ERC1155ConduitPreapproved
} from "../lib/ERC1155ConduitPreapproved.sol";

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

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

import { ERC1155 } from "solady/src/tokens/ERC1155.sol";

import { ERC2981 } from "solady/src/tokens/ERC2981.sol";
Expand All @@ -26,6 +30,7 @@ import { Ownable } from "solady/src/auth/Ownable.sol";
*/
contract ERC1155ContractMetadata is
ERC1155ConduitPreapproved,
TokenTransferValidator,
ERC2981,
Ownable,
IERC1155ContractMetadata
Expand Down Expand Up @@ -304,6 +309,46 @@ contract ERC1155ContractMetadata is
return _baseURI;
}

/**
* @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 Override this function to return true if `_beforeTokenTransfer` is used.
function _useBeforeTokenTransfer() internal view virtual override returns (bool) {
return true;
}

/**
* @dev Hook that is called before any token transfer.
* This includes minting and burning.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory /* data */
) internal virtual override {
if (from != address(0) && to != address(0)) {
// Call the transfer validator if one is set.
if (_transferValidator != address(0)) {
for (uint256 i = 0; i < ids.length; i++) {
ITransferValidator(_transferValidator).validateTransfer(
msg.sender,
from,
to,
ids[i],
amounts[i]
);
}
}
}
}

/**
* @notice Returns whether the interface is supported.
*
Expand Down
36 changes: 36 additions & 0 deletions src/lib/ERC721ContractMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {

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

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

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

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

import { Ownable } from "solady/src/auth/Ownable.sol";
Expand All @@ -24,6 +28,7 @@ import { ERC2981 } from "solady/src/tokens/ERC2981.sol";
*/
contract ERC721ContractMetadata is
ERC721AConduitPreapproved,
TokenTransferValidator,
ERC2981,
Ownable,
IERC721ContractMetadata
Expand Down Expand Up @@ -272,6 +277,37 @@ contract ERC721ContractMetadata is
return string.concat(theBaseURI, _toString(tokenId));
}

/**
* @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
34 changes: 34 additions & 0 deletions src/lib/TokenTransferValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/**
* @title TokenTransferValidator
* @notice Functionality to use a transfer validator.
*/
contract TokenTransferValidator {
/// @dev Store the transfer validator. The null address means no transfer validator is set.
address internal _transferValidator;

/// @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 _transferValidator;
}

/// @notice Set the transfer validator.
/// The external method that uses this must include access control.
function _setTransferValidator(address newValidator) internal {
address oldValidator = _transferValidator;
if (oldValidator == newValidator) {
revert SameTransferValidator();
}
_transferValidator = newValidator;
emit TransferValidatorUpdated(oldValidator, newValidator);
}
}
38 changes: 38 additions & 0 deletions src/test/MockTransferValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.17;

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

contract MockTransferValidator is ITransferValidator {
bool internal _revertOnValidate;

constructor(bool revertOnValidate) {
_revertOnValidate = revertOnValidate;
}

function validateTransfer(
address,
/* caller */
address,
/* from */
address,
/* to */
uint256 /* tokenId */
) external view {
if (_revertOnValidate) {
revert("MockTransferValidator: always reverts");
}
}

function validateTransfer(
address, /* caller */
address, /* from */
address, /* to */
uint256, /* tokenId */
uint256 /* amount */
) external view {
if (_revertOnValidate) {
revert("MockTransferValidator: always reverts");
}
}
}
Loading
Loading