diff --git a/contracts/l2/FuseController.sol b/contracts/l2/FuseController.sol index b3e16ab9..bccfa904 100644 --- a/contracts/l2/FuseController.sol +++ b/contracts/l2/FuseController.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import "./L2Registry.sol"; import "./IFuseController.sol"; -import "./IControllerUpgrade.sol"; +import "./IControllerUpgradeTarget.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; error Unauthorised(bytes32 node, address addr); @@ -24,7 +24,7 @@ error nameExpired(bytes32 node); contract FuseController is Ownable, IFuseController { L2Registry immutable registry; - IControllerUpgrade upgradeContract; + IControllerUpgradeTarget upgradeContract; // A struct to hold the unpacked data struct TokenData { @@ -252,7 +252,7 @@ contract FuseController is Ownable, IFuseController { // A function that sets the upgrade contract. function setUpgradeController( - IControllerUpgrade _upgradeContract + IControllerUpgradeTarget _upgradeContract ) external onlyOwner { upgradeContract = _upgradeContract; } diff --git a/contracts/l2/IControllerUpgrade.sol b/contracts/l2/IControllerUpgradeTarget.sol similarity index 76% rename from contracts/l2/IControllerUpgrade.sol rename to contracts/l2/IControllerUpgradeTarget.sol index e4770da3..49b0bb0b 100644 --- a/contracts/l2/IControllerUpgrade.sol +++ b/contracts/l2/IControllerUpgradeTarget.sol @@ -4,6 +4,6 @@ pragma solidity ^0.8.17; import "./IController.sol"; -interface IControllerUpgrade is IController { +interface IControllerUpgradeTarget is IController { function upgradeFrom(bytes32 node, bytes calldata extraData) external; } diff --git a/contracts/l2/IFuseController.sol b/contracts/l2/IFuseController.sol index ebcecfbd..ec61770b 100644 --- a/contracts/l2/IFuseController.sol +++ b/contracts/l2/IFuseController.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import "./IController.sol"; -import "./IControllerUpgrade.sol"; +import "./IControllerUpgradeTarget.sol"; interface IFuseController is IController { function expiryOf(bytes32 node) external view returns (uint64); @@ -14,6 +14,6 @@ interface IFuseController is IController { function upgrade(bytes32 node, bytes calldata extraData) external; function setUpgradeController( - IControllerUpgrade _upgradeController + IControllerUpgradeTarget _upgradeController ) external; } diff --git a/contracts/l2/L2Registry.sol b/contracts/l2/L2Registry.sol index 87735ac5..4cfa348e 100644 --- a/contracts/l2/L2Registry.sol +++ b/contracts/l2/L2Registry.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import "@openzeppelin/contracts/interfaces/IERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import "@openzeppelin/contracts/interfaces/IERC165.sol"; import "@openzeppelin/contracts/utils/Create2.sol"; import "@openzeppelin/contracts/utils/Address.sol"; @@ -12,6 +13,8 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import "./IController.sol"; contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { + using Address for address; + struct Record { string name; bytes data; @@ -25,6 +28,8 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { error TokenDoesNotExist(uint256 id); + event NewController(uint256 id, address controller); + constructor(bytes memory root, IMetadataService _metadataService) { tokens[0].data = root; metadataService = _metadataService; @@ -61,6 +66,8 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { ) external { _safeTransferFrom(from, to, id, value, data); emit TransferSingle(msg.sender, from, to, id, value); + + _doSafeTransferAcceptanceCheck(msg.sender, from, to, id, value, data); } function safeBatchTransferFrom( @@ -75,6 +82,15 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { _safeTransferFrom(from, to, ids[i], values[i], data); } emit TransferBatch(msg.sender, from, to, ids, values); + + _doSafeBatchTransferAcceptanceCheck( + msg.sender, + from, + to, + ids, + values, + data + ); } function setApprovalForAll(address operator, bool approved) external { @@ -215,6 +231,12 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { // Only the controller may call this function require(address(oldController) == msg.sender); + // Fetch the new controller and emit `NewController` if needed. + IController newController = _getController(data); + if (oldController != newController) { + emit NewController(id, address(newController)); + } + // Update the data for this node. tokens[id].data = data; } @@ -240,13 +262,29 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { ? address(0) : oldSubnodeController.ownerOfWithData(oldSubnodeData); + // Get the address of the new controller + IController newSubnodeController = _getController(subnodeData); + if (newSubnodeController != oldSubnodeController) { + emit NewController(subnode, address(newSubnodeController)); + } + tokens[subnode].data = subnodeData; // Fetch the to address, if not supplied, for the TransferSingle event. if (to == address(0) && subnodeData.length >= 20) { to = _getController(subnodeData).ownerOfWithData(subnodeData); } + emit TransferSingle(operator, oldOwner, to, subnode, 1); + + _doSafeTransferAcceptanceCheck( + operator, + oldOwner, + to, + subnode, + 1, + bytes("") + ); } /********************** @@ -292,4 +330,67 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI { tokens[id].data = newTokenData; } + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155Received( + operator, + from, + id, + amount, + data + ) + returns (bytes4 response) { + if ( + response != IERC1155Receiver(to).onERC1155Received.selector + ) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155BatchReceived( + operator, + from, + ids, + amounts, + data + ) + returns (bytes4 response) { + if ( + response != + IERC1155Receiver(to).onERC1155BatchReceived.selector + ) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } } diff --git a/contracts/l2/mocks/FuseControllerUpgraded.sol b/contracts/l2/mocks/FuseControllerUpgraded.sol index 07e7ff8c..76a8162f 100644 --- a/contracts/l2/mocks/FuseControllerUpgraded.sol +++ b/contracts/l2/mocks/FuseControllerUpgraded.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import "../L2Registry.sol"; import "../IFuseController.sol"; -import "../IControllerUpgrade.sol"; +import "../IControllerUpgradeTarget.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "hardhat/console.sol"; @@ -26,11 +26,11 @@ error nameExpired(bytes32 node); contract FuseControllerUpgraded is Ownable, IFuseController, - IControllerUpgrade + IControllerUpgradeTarget { L2Registry immutable registry; - IControllerUpgrade upgradeContract; + IControllerUpgradeTarget upgradeContract; // A struct to hold the unpacked data struct TokenData { @@ -246,7 +246,7 @@ contract FuseControllerUpgraded is // A function that sets the upgrade contract. function setUpgradeController( - IControllerUpgrade _upgradeContract + IControllerUpgradeTarget _upgradeContract ) external onlyOwner { upgradeContract = _upgradeContract; }