From f8c103360d8973af69258d1c566b336a2dffd3d5 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 26 Mar 2024 14:53:29 -0700 Subject: [PATCH 1/2] add transfer validator --- config/.solhintignore | 2 + .../src/ERC721ContractMetadataStorage.sol | 3 + .../src/ERC721ContractMetadataUpgradeable.sol | 55 +++++++++--- .../src/interfaces/ITransferValidator.sol | 21 +++++ .../ERC721AConduitPreapprovedUpgradeable.sol | 32 +++++++ .../ERC721TransferValidatorUpgradeable.sol | 35 ++++++++ src/ERC721ContractMetadata.sol | 39 +++++++- .../ERC721AConduitPreapprovedCloneable.sol | 31 +++++++ .../ERC721ContractMetadataCloneable.sol | 42 ++++++++- src/interfaces/ITransferValidator.sol | 22 +++++ src/lib/ERC721TransferValidator.sol | 34 +++++++ src/test/MockTransferValidator.sol | 38 ++++++++ test/foundry/ERC721TransferValidator.t.sol | 90 +++++++++++++++++++ 13 files changed, 430 insertions(+), 14 deletions(-) create mode 100644 src-upgradeable/src/interfaces/ITransferValidator.sol create mode 100644 src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol create mode 100644 src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol create mode 100644 src/clones/ERC721AConduitPreapprovedCloneable.sol create mode 100644 src/interfaces/ITransferValidator.sol create mode 100644 src/lib/ERC721TransferValidator.sol create mode 100644 src/test/MockTransferValidator.sol create mode 100644 test/foundry/ERC721TransferValidator.t.sol diff --git a/config/.solhintignore b/config/.solhintignore index cd5f7f13..c28c7375 100644 --- a/config/.solhintignore +++ b/config/.solhintignore @@ -4,5 +4,7 @@ src/clones src/test/ src/shim/ +src/interfaces/ITransferValidator.sol + test/ lib/ \ No newline at end of file diff --git a/src-upgradeable/src/ERC721ContractMetadataStorage.sol b/src-upgradeable/src/ERC721ContractMetadataStorage.sol index 995351c6..28affdab 100644 --- a/src-upgradeable/src/ERC721ContractMetadataStorage.sol +++ b/src-upgradeable/src/ERC721ContractMetadataStorage.sol @@ -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 = diff --git a/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol b/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol index 9dd2a023..b7fa1a60 100644 --- a/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol +++ b/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol @@ -6,8 +6,12 @@ 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 { TwoStepOwnableUpgradeable @@ -34,7 +38,7 @@ import { * with additional metadata and ownership capabilities. */ contract ERC721ContractMetadataUpgradeable is - ERC721AUpgradeable, + ERC721AConduitPreapprovedUpgradeable, TwoStepOwnableUpgradeable, ISeaDropTokenContractMetadataUpgradeable { @@ -61,17 +65,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. * @@ -270,7 +268,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. @@ -286,6 +285,38 @@ 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, + tokenId + ); + } + } + } + /** * @notice Returns whether the interface is supported. * @@ -295,7 +326,7 @@ contract ERC721ContractMetadataUpgradeable is public view virtual - override(IERC165Upgradeable, ERC721AUpgradeable) + override(IERC165Upgradeable, ERC721AConduitPreapprovedUpgradeable) returns (bool) { return diff --git a/src-upgradeable/src/interfaces/ITransferValidator.sol b/src-upgradeable/src/interfaces/ITransferValidator.sol new file mode 100644 index 00000000..9c5cc0a0 --- /dev/null +++ b/src-upgradeable/src/interfaces/ITransferValidator.sol @@ -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; +} diff --git a/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol b/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol new file mode 100644 index 00000000..a1b2f60d --- /dev/null +++ b/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol @@ -0,0 +1,32 @@ +// 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); + } +} diff --git a/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol b/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol new file mode 100644 index 00000000..58815b30 --- /dev/null +++ b/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol @@ -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 = _transferValidator; + if (oldValidator == newValidator) { + revert SameTransferValidator(); + } + ERC721ContractMetadataStorage.layout()._transferValidator = newValidator; + emit TransferValidatorUpdated(oldValidator, newValidator); + } +} diff --git a/src/ERC721ContractMetadata.sol b/src/ERC721ContractMetadata.sol index a8a25838..3a9884cf 100644 --- a/src/ERC721ContractMetadata.sol +++ b/src/ERC721ContractMetadata.sol @@ -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"; @@ -27,6 +31,7 @@ import { */ contract ERC721ContractMetadata is ERC721AConduitPreapproved, + ERC721TransferValidator, TwoStepOwnable, ISeaDropTokenContractMetadata { @@ -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. @@ -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. * diff --git a/src/clones/ERC721AConduitPreapprovedCloneable.sol b/src/clones/ERC721AConduitPreapprovedCloneable.sol new file mode 100644 index 00000000..8826591f --- /dev/null +++ b/src/clones/ERC721AConduitPreapprovedCloneable.sol @@ -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); + } +} diff --git a/src/clones/ERC721ContractMetadataCloneable.sol b/src/clones/ERC721ContractMetadataCloneable.sol index cff4cf7a..2d63359f 100644 --- a/src/clones/ERC721ContractMetadataCloneable.sol +++ b/src/clones/ERC721ContractMetadataCloneable.sol @@ -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"; @@ -24,7 +32,8 @@ import { * with additional metadata and ownership capabilities. */ contract ERC721ContractMetadataCloneable is - ERC721ACloneable, + ERC721AConduitPreapprovedCloneable, + ERC721TransferValidator, TwoStepOwnable, ISeaDropTokenContractMetadata { @@ -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. * diff --git a/src/interfaces/ITransferValidator.sol b/src/interfaces/ITransferValidator.sol new file mode 100644 index 00000000..cc26f2d6 --- /dev/null +++ b/src/interfaces/ITransferValidator.sol @@ -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; +} diff --git a/src/lib/ERC721TransferValidator.sol b/src/lib/ERC721TransferValidator.sol new file mode 100644 index 00000000..6028c524 --- /dev/null +++ b/src/lib/ERC721TransferValidator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +/** + * @title ERC721TransferValidator + * @notice Functionality to use a transfer validator. + */ +contract ERC721TransferValidator { + /// @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); + } +} diff --git a/src/test/MockTransferValidator.sol b/src/test/MockTransferValidator.sol new file mode 100644 index 00000000..7617c74a --- /dev/null +++ b/src/test/MockTransferValidator.sol @@ -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"); + } + } +} diff --git a/test/foundry/ERC721TransferValidator.t.sol b/test/foundry/ERC721TransferValidator.t.sol new file mode 100644 index 00000000..5276f7fa --- /dev/null +++ b/test/foundry/ERC721TransferValidator.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { TestHelper } from "test/foundry/utils/TestHelper.sol"; + +import { ERC721SeaDrop } from "seadrop/ERC721SeaDrop.sol"; + +import { MockTransferValidator } from "seadrop/test/MockTransferValidator.sol"; + +import { TwoStepOwnable } from "utility-contracts/TwoStepOwnable.sol"; + +contract ERC721SeaDropWithMint is ERC721SeaDrop { + constructor( + string memory name, + string memory symbol, + address[] memory allowedSeaDrop + ) ERC721SeaDrop(name, symbol, allowedSeaDrop) {} + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} + +contract ERC721TransferValidatorTest is TestHelper { + MockTransferValidator transferValidatorAlwaysSucceeds = + new MockTransferValidator(false); + MockTransferValidator transferValidatorAlwaysReverts = + new MockTransferValidator(true); + + ERC721SeaDropWithMint token_; + + event TransferValidatorUpdated(address oldValidator, address newValidator); + + function setUp() public { + token_ = new ERC721SeaDropWithMint("", "", new address[](0)); + } + + function testOnlyOwnerCanSetTransferValidator() public { + assertEq(token_.getTransferValidator(), address(0)); + + vm.prank(address(token_)); + vm.expectRevert(TwoStepOwnable.OnlyOwner.selector); + token_.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + + token_.setTransferValidator(address(transferValidatorAlwaysSucceeds)); + assertEq( + token_.getTransferValidator(), + address(transferValidatorAlwaysSucceeds) + ); + } + + function testTransferValidatorIsCalledOnTransfer() public { + 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 onERC721Received( + address, + address, + uint256, + bytes calldata + ) public pure returns (bytes4) { + return this.onERC721Received.selector; + } +} From 15d668240bf570c0ce24bb645173f2bd7eeb35f1 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 28 Mar 2024 16:12:37 -0700 Subject: [PATCH 2/2] fixes --- .../src/ERC721ContractMetadataUpgradeable.sol | 9 +++++---- .../src/lib/ERC721AConduitPreapprovedUpgradeable.sol | 3 ++- .../src/lib/ERC721TransferValidatorUpgradeable.sol | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol b/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol index b7fa1a60..561f8cb7 100644 --- a/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol +++ b/src-upgradeable/src/ERC721ContractMetadataUpgradeable.sol @@ -13,6 +13,8 @@ import { ERC721TransferValidatorUpgradeable } from "./lib/ERC721TransferValidatorUpgradeable.sol"; +import { ERC721AUpgradeable } from "../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol"; + import { TwoStepOwnableUpgradeable } from "../lib-upgradeable/utility-contracts/src/TwoStepOwnableUpgradeable.sol"; @@ -300,8 +302,7 @@ contract ERC721ContractMetadataUpgradeable is function _beforeTokenTransfers( address from, address to, - uint256, - /* startTokenId */ + uint256 startTokenId, uint256 /* quantity */ ) internal virtual override { if (from != address(0) && to != address(0)) { @@ -311,7 +312,7 @@ contract ERC721ContractMetadataUpgradeable is msg.sender, from, to, - tokenId + startTokenId ); } } @@ -326,7 +327,7 @@ contract ERC721ContractMetadataUpgradeable is public view virtual - override(IERC165Upgradeable, ERC721AConduitPreapprovedUpgradeable) + override(IERC165Upgradeable, ERC721AUpgradeable) returns (bool) { return diff --git a/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol b/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol index a1b2f60d..f3e7eed6 100644 --- a/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol +++ b/src-upgradeable/src/lib/ERC721AConduitPreapprovedUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import {ERC721AUpgradeable} from "../../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol"; +import { ERC721AUpgradeable } from "../../lib/ERC721A-Upgradeable/contracts/ERC721AUpgradeable.sol"; /** * @title ERC721AConduitPreapproved @@ -18,6 +18,7 @@ abstract contract ERC721AConduitPreapprovedUpgradeable is ERC721AUpgradeable { 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 diff --git a/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol b/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol index 58815b30..80cc2cfd 100644 --- a/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol +++ b/src-upgradeable/src/lib/ERC721TransferValidatorUpgradeable.sol @@ -25,7 +25,7 @@ contract ERC721TransferValidatorUpgradeable { /// @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 = ERC721ContractMetadataStorage.layout()._transferValidator; if (oldValidator == newValidator) { revert SameTransferValidator(); }