Skip to content

Commit

Permalink
add transfer validator to v2 (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio authored Mar 29, 2024
1 parent 4643060 commit c86df49
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 37 deletions.
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

0 comments on commit c86df49

Please sign in to comment.