From c425dbddc1b40f78ce2e87fc2ad5b5eb0d787926 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 8 Apr 2024 11:48:42 -0700 Subject: [PATCH 1/4] add transfer validator contracts --- .../transfer-validated/ICreatorToken.sol | 12 +++ .../transfer-validated/ITransferValidator.sol | 12 +++ .../ERC1155ShipyardTransferValidated.sol | 77 +++++++++++++ .../ERC721ShipyardTransferValidated.sol | 64 +++++++++++ .../lib/TokenTransferValidator.sol | 33 ++++++ .../ERC1155ShipyardTransferValidated.t.sol | 101 ++++++++++++++++++ .../ERC721ShipyardTransferValidated.t.sol | 81 ++++++++++++++ .../mock/MockTransferValidator.sol | 38 +++++++ 8 files changed, 418 insertions(+) create mode 100644 src/interfaces/transfer-validated/ICreatorToken.sol create mode 100644 src/interfaces/transfer-validated/ITransferValidator.sol create mode 100644 src/transfer-validated/ERC1155ShipyardTransferValidated.sol create mode 100644 src/transfer-validated/ERC721ShipyardTransferValidated.sol create mode 100644 src/transfer-validated/lib/TokenTransferValidator.sol create mode 100644 test/transfer-validated/ERC1155ShipyardTransferValidated.t.sol create mode 100644 test/transfer-validated/ERC721ShipyardTransferValidated.t.sol create mode 100644 test/transfer-validated/mock/MockTransferValidator.sol diff --git a/src/interfaces/transfer-validated/ICreatorToken.sol b/src/interfaces/transfer-validated/ICreatorToken.sol new file mode 100644 index 0000000..33da367 --- /dev/null +++ b/src/interfaces/transfer-validated/ICreatorToken.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface ICreatorToken { + event TransferValidatorUpdated(address oldValidator, address newValidator); + + function getTransferValidator() external view returns (address validator); + + function getTransferValidationFunction() external view returns (bytes4 functionSignature, bool isViewFunction); + + function setTransferValidator(address validator) external; +} diff --git a/src/interfaces/transfer-validated/ITransferValidator.sol b/src/interfaces/transfer-validated/ITransferValidator.sol new file mode 100644 index 0000000..d5294bc --- /dev/null +++ b/src/interfaces/transfer-validated/ITransferValidator.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface ITransferValidator721 { + /// @notice Ensure that a transfer has been authorized for a specific tokenId + function validateTransfer(address caller, address from, address to, uint256 tokenId) external view; +} + +interface ITransferValidator1155 { + /// @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; +} diff --git a/src/transfer-validated/ERC1155ShipyardTransferValidated.sol b/src/transfer-validated/ERC1155ShipyardTransferValidated.sol new file mode 100644 index 0000000..b2fd0e3 --- /dev/null +++ b/src/transfer-validated/ERC1155ShipyardTransferValidated.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ERC1155} from "solady/src/tokens/ERC1155.sol"; +import {ERC1155ConduitPreapproved_Solady} from "../tokens/erc1155/ERC1155ConduitPreapproved_Solady.sol"; +import {TokenTransferValidator} from "./lib/TokenTransferValidator.sol"; +import {ICreatorToken} from "../interfaces/transfer-validated/ICreatorToken.sol"; +import {ITransferValidator1155} from "../interfaces/transfer-validated/ITransferValidator.sol"; + +contract ERC1155ShipyardTransferValidated is ERC1155ConduitPreapproved_Solady, TokenTransferValidator, Ownable { + constructor(address initialTransferValidator) ERC1155ConduitPreapproved_Solady() { + // Set the initial contract owner. + _initializeOwner(msg.sender); + + // Set the initial transfer validator. + if (initialTransferValidator != address(0)) { + _setTransferValidator(initialTransferValidator); + } + } + + /// @notice Returns the transfer validation function used. + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = ITransferValidator1155.validateTransfer.selector; + isViewFunction = true; + } + + /// @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. + address transferValidator = _transferValidator; + if (transferValidator != address(0)) { + for (uint256 i = 0; i < ids.length; i++) { + ITransferValidator1155(transferValidator).validateTransfer(msg.sender, from, to, ids[i], amounts[i]); + } + } + } + } + + /// @dev Override supportsInterface to additionally return true for ICreatorToken. + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155) returns (bool) { + return interfaceId == type(ICreatorToken).interfaceId || ERC1155.supportsInterface(interfaceId); + } + + /// @dev Replace me with the token name. + function name() public view virtual returns (string memory) { + return "ERC1155ShipyardTransferValidated"; + } + + /// @dev Replace me with the token symbol. + function symbol() public view virtual returns (string memory) { + return "ERC1155-S-TV"; + } + + /// @dev Replace me with the token URI. + function uri(uint256 /* id */ ) public view virtual override returns (string memory) { + return ""; + } +} diff --git a/src/transfer-validated/ERC721ShipyardTransferValidated.sol b/src/transfer-validated/ERC721ShipyardTransferValidated.sol new file mode 100644 index 0000000..852410e --- /dev/null +++ b/src/transfer-validated/ERC721ShipyardTransferValidated.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ERC721} from "solady/src/tokens/ERC721.sol"; +import {ERC721ConduitPreapproved_Solady} from "../tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; +import {TokenTransferValidator} from "./lib/TokenTransferValidator.sol"; +import {ICreatorToken} from "../interfaces/transfer-validated/ICreatorToken.sol"; +import {ITransferValidator721} from "../interfaces/transfer-validated/ITransferValidator.sol"; + +contract ERC721ShipyardTransferValidated is ERC721ConduitPreapproved_Solady, TokenTransferValidator, Ownable { + constructor(address initialTransferValidator) ERC721ConduitPreapproved_Solady() { + // Set the initial contract owner. + _initializeOwner(msg.sender); + + // Set the initial transfer validator. + if (initialTransferValidator != address(0)) { + _setTransferValidator(initialTransferValidator); + } + } + + /// @notice Returns the transfer validation function used. + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = ITransferValidator721.validateTransfer.selector; + isViewFunction = false; + } + + /// @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 _beforeTokenTransfer(address from, address to, uint256 id) internal virtual override { + if (from != address(0) && to != address(0)) { + // Call the transfer validator if one is set. + address transferValidator = _transferValidator; + if (transferValidator != address(0)) { + ITransferValidator721(transferValidator).validateTransfer(msg.sender, from, to, id); + } + } + } + + /// @dev Override supportsInterface to additionally return true for ICreatorToken. + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) { + return interfaceId == type(ICreatorToken).interfaceId || ERC721.supportsInterface(interfaceId); + } + + /// @dev Replace me with the token name. + function name() public view virtual override returns (string memory) { + return "ERC721ShipyardTransferValidated"; + } + + /// @dev Replace me with the token symbol. + function symbol() public view virtual override returns (string memory) { + return "ERC721-S-TV"; + } + + /// @dev Replace me with the token URI. + function tokenURI(uint256 /* id */ ) public view virtual override returns (string memory) { + return ""; + } +} diff --git a/src/transfer-validated/lib/TokenTransferValidator.sol b/src/transfer-validated/lib/TokenTransferValidator.sol new file mode 100644 index 0000000..360c08c --- /dev/null +++ b/src/transfer-validated/lib/TokenTransferValidator.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {ICreatorToken} from "../../interfaces/transfer-validated/ICreatorToken.sol"; + +/** + * @title TokenTransferValidator + * @notice Functionality to use a transfer validator. + */ +abstract contract TokenTransferValidator is ICreatorToken { + /// @dev Store the transfer validator. The null address means no transfer validator is set. + address internal _transferValidator; + + /// @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); + } +} diff --git a/test/transfer-validated/ERC1155ShipyardTransferValidated.t.sol b/test/transfer-validated/ERC1155ShipyardTransferValidated.t.sol new file mode 100644 index 0000000..0dd03ae --- /dev/null +++ b/test/transfer-validated/ERC1155ShipyardTransferValidated.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; +import {TestPlus} from "solady/test/utils/TestPlus.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ICreatorToken} from "src/interfaces/transfer-validated/ICreatorToken.sol"; +import {ITransferValidator1155} from "src/interfaces/transfer-validated/ITransferValidator.sol"; +import {MockTransferValidator} from "./mock/MockTransferValidator.sol"; +import {ERC1155ShipyardTransferValidated} from "src/transfer-validated/ERC1155ShipyardTransferValidated.sol"; + +contract ERC1155ShipyardTransferValidatedWithMint is ERC1155ShipyardTransferValidated { + constructor(address initialTransferValidator) ERC1155ShipyardTransferValidated(initialTransferValidator) {} + + function mint(address to, uint256 id, uint256 amount) public onlyOwner { + _mint(to, id, amount, ""); + } +} + +contract TestERC1155ShipyardTransferValidated is Test, TestPlus { + MockTransferValidator transferValidatorAlwaysSucceeds = new MockTransferValidator(false); + MockTransferValidator transferValidatorAlwaysReverts = new MockTransferValidator(true); + + event TransferValidatorUpdated(address oldValidator, address newValidator); + + ERC1155ShipyardTransferValidatedWithMint token; + + function setUp() public { + token = new ERC1155ShipyardTransferValidatedWithMint(address(0)); + } + + function testOnlyOwnerCanSetTransferValidator() public { + assertEq(token.getTransferValidator(), address(0)); + + vm.prank(address(token)); + vm.expectRevert(Ownable.Unauthorized.selector); + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + assertEq(token.getTransferValidator(), address(transferValidatorAlwaysSucceeds)); + } + + function testTransferValidatedSetInConstructor() public { + ERC1155ShipyardTransferValidatedWithMint token2 = + new ERC1155ShipyardTransferValidatedWithMint(address(transferValidatorAlwaysSucceeds)); + + assertEq(token2.getTransferValidator(), address(transferValidatorAlwaysSucceeds)); + } + + function testTransferValidatorIsCalledOnTransfer() public { + token.mint(address(this), 1, 10); + token.mint(address(this), 2, 10); + + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(0), address(transferValidatorAlwaysSucceeds)); + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + token.safeTransferFrom(address(this), msg.sender, 1, 1, ""); + uint256[] memory ids = new uint256[](2); + uint256[] memory amounts = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + amounts[0] = 2; + amounts[1] = 2; + token.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, ""); + + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(transferValidatorAlwaysSucceeds), address(transferValidatorAlwaysReverts)); + token.setTransferValidator(address(transferValidatorAlwaysReverts)); + vm.expectRevert("MockTransferValidator: always reverts"); + token.safeTransferFrom(address(this), msg.sender, 1, 1, ""); + vm.expectRevert("MockTransferValidator: always reverts"); + token.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, ""); + + // When set to null address, transfer should succeed without calling the validator + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(transferValidatorAlwaysReverts), address(0)); + token.setTransferValidator(address(0)); + token.safeTransferFrom(address(this), msg.sender, 1, 1, ""); + token.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, ""); + } + + function testGetTransferValidationFunction() public { + (bytes4 functionSignature, bool isViewFunction) = token.getTransferValidationFunction(); + assertEq(functionSignature, ITransferValidator1155.validateTransfer.selector); + assertEq(isViewFunction, true); + } + + function testSupportsInterface() public { + assertEq(token.supportsInterface(type(ICreatorToken).interfaceId), true); + } + + function onERC1155Received( + address, /* operator */ + address, /* from */ + uint256, /* id */ + uint256, /* value */ + bytes calldata /* data */ + ) external pure returns (bytes4) { + return this.onERC1155Received.selector; + } +} diff --git a/test/transfer-validated/ERC721ShipyardTransferValidated.t.sol b/test/transfer-validated/ERC721ShipyardTransferValidated.t.sol new file mode 100644 index 0000000..d8d6e71 --- /dev/null +++ b/test/transfer-validated/ERC721ShipyardTransferValidated.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; +import {TestPlus} from "solady/test/utils/TestPlus.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ICreatorToken} from "src/interfaces/transfer-validated/ICreatorToken.sol"; +import {ITransferValidator721} from "src/interfaces/transfer-validated/ITransferValidator.sol"; +import {MockTransferValidator} from "./mock/MockTransferValidator.sol"; +import {ERC721ShipyardTransferValidated} from "src/transfer-validated/ERC721ShipyardTransferValidated.sol"; + +contract ERC721ShipyardTransferValidatedWithMint is ERC721ShipyardTransferValidated { + constructor(address initialTransferValidator) ERC721ShipyardTransferValidated(initialTransferValidator) {} + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} + +contract TestERC721ShipyardTransferValidated is Test, TestPlus { + MockTransferValidator transferValidatorAlwaysSucceeds = new MockTransferValidator(false); + MockTransferValidator transferValidatorAlwaysReverts = new MockTransferValidator(true); + + event TransferValidatorUpdated(address oldValidator, address newValidator); + + ERC721ShipyardTransferValidatedWithMint token; + + function setUp() public { + token = new ERC721ShipyardTransferValidatedWithMint(address(0)); + } + + function testOnlyOwnerCanSetTransferValidator() public { + assertEq(token.getTransferValidator(), address(0)); + + vm.prank(address(token)); + vm.expectRevert(Ownable.Unauthorized.selector); + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + assertEq(token.getTransferValidator(), address(transferValidatorAlwaysSucceeds)); + } + + function testTransferValidatedSetInConstructor() public { + ERC721ShipyardTransferValidatedWithMint token2 = + new ERC721ShipyardTransferValidatedWithMint(address(transferValidatorAlwaysSucceeds)); + + assertEq(token2.getTransferValidator(), address(transferValidatorAlwaysSucceeds)); + } + + function testTransferValidatorIsCalledOnTransfer() public { + token.mint(address(this), 1); + token.mint(address(this), 2); + + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(0), address(transferValidatorAlwaysSucceeds)); + token.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + token.safeTransferFrom(address(this), msg.sender, 1); + + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(transferValidatorAlwaysSucceeds), address(transferValidatorAlwaysReverts)); + token.setTransferValidator(address(transferValidatorAlwaysReverts)); + vm.expectRevert("MockTransferValidator: always reverts"); + token.safeTransferFrom(address(this), msg.sender, 2); + + // When set to null address, transfer should succeed without calling the validator + vm.expectEmit(true, true, true, true); + emit TransferValidatorUpdated(address(transferValidatorAlwaysReverts), address(0)); + token.setTransferValidator(address(0)); + token.safeTransferFrom(address(this), msg.sender, 2); + } + + function testGetTransferValidationFunction() public { + (bytes4 functionSignature, bool isViewFunction) = token.getTransferValidationFunction(); + assertEq(functionSignature, ITransferValidator721.validateTransfer.selector); + assertEq(isViewFunction, false); + } + + function testSupportsInterface() public { + assertEq(token.supportsInterface(type(ICreatorToken).interfaceId), true); + } +} diff --git a/test/transfer-validated/mock/MockTransferValidator.sol b/test/transfer-validated/mock/MockTransferValidator.sol new file mode 100644 index 0000000..7c32486 --- /dev/null +++ b/test/transfer-validated/mock/MockTransferValidator.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import {ITransferValidator721, ITransferValidator1155} from "src/interfaces/transfer-validated/ITransferValidator.sol"; + +contract MockTransferValidator is ITransferValidator721, ITransferValidator1155 { + 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"); + } + } +} From 1e86080c3b65fe2c49c4910741b42901ad147976 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 8 Apr 2024 12:40:02 -0700 Subject: [PATCH 2/4] use upgradable storage --- .../ERC1155ShipyardTransferValidated.sol | 6 +++-- .../ERC721ShipyardTransferValidated.sol | 6 +++-- .../lib/TokenTransferValidator.sol | 25 +++++++++++++++---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/transfer-validated/ERC1155ShipyardTransferValidated.sol b/src/transfer-validated/ERC1155ShipyardTransferValidated.sol index b2fd0e3..ccc40cd 100644 --- a/src/transfer-validated/ERC1155ShipyardTransferValidated.sol +++ b/src/transfer-validated/ERC1155ShipyardTransferValidated.sol @@ -4,11 +4,13 @@ pragma solidity ^0.8.17; import {Ownable} from "solady/src/auth/Ownable.sol"; import {ERC1155} from "solady/src/tokens/ERC1155.sol"; import {ERC1155ConduitPreapproved_Solady} from "../tokens/erc1155/ERC1155ConduitPreapproved_Solady.sol"; -import {TokenTransferValidator} from "./lib/TokenTransferValidator.sol"; +import {TokenTransferValidator, TokenTransferValidatorStorage} from "./lib/TokenTransferValidator.sol"; import {ICreatorToken} from "../interfaces/transfer-validated/ICreatorToken.sol"; import {ITransferValidator1155} from "../interfaces/transfer-validated/ITransferValidator.sol"; contract ERC1155ShipyardTransferValidated is ERC1155ConduitPreapproved_Solady, TokenTransferValidator, Ownable { + using TokenTransferValidatorStorage for TokenTransferValidatorStorage.Layout; + constructor(address initialTransferValidator) ERC1155ConduitPreapproved_Solady() { // Set the initial contract owner. _initializeOwner(msg.sender); @@ -46,7 +48,7 @@ contract ERC1155ShipyardTransferValidated is ERC1155ConduitPreapproved_Solady, T ) internal virtual override { if (from != address(0) && to != address(0)) { // Call the transfer validator if one is set. - address transferValidator = _transferValidator; + address transferValidator = TokenTransferValidatorStorage.layout()._transferValidator; if (transferValidator != address(0)) { for (uint256 i = 0; i < ids.length; i++) { ITransferValidator1155(transferValidator).validateTransfer(msg.sender, from, to, ids[i], amounts[i]); diff --git a/src/transfer-validated/ERC721ShipyardTransferValidated.sol b/src/transfer-validated/ERC721ShipyardTransferValidated.sol index 852410e..3a1fe52 100644 --- a/src/transfer-validated/ERC721ShipyardTransferValidated.sol +++ b/src/transfer-validated/ERC721ShipyardTransferValidated.sol @@ -4,11 +4,13 @@ pragma solidity ^0.8.17; import {Ownable} from "solady/src/auth/Ownable.sol"; import {ERC721} from "solady/src/tokens/ERC721.sol"; import {ERC721ConduitPreapproved_Solady} from "../tokens/erc721/ERC721ConduitPreapproved_Solady.sol"; -import {TokenTransferValidator} from "./lib/TokenTransferValidator.sol"; +import {TokenTransferValidator, TokenTransferValidatorStorage} from "./lib/TokenTransferValidator.sol"; import {ICreatorToken} from "../interfaces/transfer-validated/ICreatorToken.sol"; import {ITransferValidator721} from "../interfaces/transfer-validated/ITransferValidator.sol"; contract ERC721ShipyardTransferValidated is ERC721ConduitPreapproved_Solady, TokenTransferValidator, Ownable { + using TokenTransferValidatorStorage for TokenTransferValidatorStorage.Layout; + constructor(address initialTransferValidator) ERC721ConduitPreapproved_Solady() { // Set the initial contract owner. _initializeOwner(msg.sender); @@ -35,7 +37,7 @@ contract ERC721ShipyardTransferValidated is ERC721ConduitPreapproved_Solady, Tok function _beforeTokenTransfer(address from, address to, uint256 id) internal virtual override { if (from != address(0) && to != address(0)) { // Call the transfer validator if one is set. - address transferValidator = _transferValidator; + address transferValidator = TokenTransferValidatorStorage.layout()._transferValidator; if (transferValidator != address(0)) { ITransferValidator721(transferValidator).validateTransfer(msg.sender, from, to, id); } diff --git a/src/transfer-validated/lib/TokenTransferValidator.sol b/src/transfer-validated/lib/TokenTransferValidator.sol index 360c08c..74a563b 100644 --- a/src/transfer-validated/lib/TokenTransferValidator.sol +++ b/src/transfer-validated/lib/TokenTransferValidator.sol @@ -3,13 +3,28 @@ pragma solidity ^0.8.17; import {ICreatorToken} from "../../interfaces/transfer-validated/ICreatorToken.sol"; +library TokenTransferValidatorStorage { + struct Layout { + /// @dev Store the transfer validator. The null address means no transfer validator is set. + address _transferValidator; + } + + bytes32 internal constant STORAGE_SLOT = keccak256("contracts.storage.tokenTransferValidator"); + + function layout() internal pure returns (Layout storage l) { + bytes32 slot = STORAGE_SLOT; + assembly { + l.slot := slot + } + } +} + /** * @title TokenTransferValidator * @notice Functionality to use a transfer validator. */ abstract contract TokenTransferValidator is ICreatorToken { - /// @dev Store the transfer validator. The null address means no transfer validator is set. - address internal _transferValidator; + using TokenTransferValidatorStorage for TokenTransferValidatorStorage.Layout; /// @notice Revert with an error if the transfer validator is being set to the same address. error SameTransferValidator(); @@ -17,17 +32,17 @@ abstract contract TokenTransferValidator is ICreatorToken { /// @notice Returns the currently active transfer validator. /// The null address means no transfer validator is set. function getTransferValidator() external view returns (address) { - return _transferValidator; + return TokenTransferValidatorStorage.layout()._transferValidator; } /// @notice Set the transfer validator. /// The external method that uses this must include access control. function _setTransferValidator(address newValidator) internal { - address oldValidator = _transferValidator; + address oldValidator = TokenTransferValidatorStorage.layout()._transferValidator; if (oldValidator == newValidator) { revert SameTransferValidator(); } - _transferValidator = newValidator; + TokenTransferValidatorStorage.layout()._transferValidator = newValidator; emit TransferValidatorUpdated(oldValidator, newValidator); } } From 2ee13e093c7f5aee04944b79dadcf16089cbb560 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Mon, 8 Apr 2024 19:41:21 +0000 Subject: [PATCH 3/4] Github Actions automatically updated formatting with forge fmt --- src/reference/ExampleNFT.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reference/ExampleNFT.sol b/src/reference/ExampleNFT.sol index 90908ce..9be9141 100644 --- a/src/reference/ExampleNFT.sol +++ b/src/reference/ExampleNFT.sol @@ -71,10 +71,10 @@ contract ExampleNFT is AbstractNFT { svg.prop("text-anchor", "middle"), svg.prop("font-size", "48"), svg.prop("fill", "black") - ), + ), children: LibString.toString(tokenId) }) - ) + ) }); } From fee74c5c8e2f6679d391ebcae48887667af9e6ec Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Mon, 8 Apr 2024 19:41:21 +0000 Subject: [PATCH 4/4] Updated .git-blame-ignore-revs with commit 2ee13e093c7f5aee04944b79dadcf16089cbb560 --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 924ff41..22683ce 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -3,3 +3,4 @@ # Github Actions automatically updated formatting with forge fmt\n2d96311b29055c5b9a0b632176ce3b8d78a23a89 # Github Actions automatically updated formatting with forge fmt\ne2d336bbb0331c7716c16fed64f51be6c270ad02 # Github Actions automatically updated formatting with forge fmt\n3458423c8716bdcd3875cde4597dcae377a16620 +# Github Actions automatically updated formatting with forge fmt\n2ee13e093c7f5aee04944b79dadcf16089cbb560