diff --git a/src/CCIP2ETH.sol b/src/CCIP2ETH.sol index 0278495..e72d3ea 100644 --- a/src/CCIP2ETH.sol +++ b/src/CCIP2ETH.sol @@ -62,7 +62,7 @@ contract CCIP2ETH is iCCIP2ETH { /// @dev - Constructor constructor(address _gateway) { gateway = iGatewayManager(_gateway); - chainID = gateway.uintToString(block.chainid); + chainID = block.chainid == 1 ? "1" : "5"; // Set ChainID /// @dev - Sets ENS Mainnet wrapper as Wrapper isWrapper[0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401] = true; emit UpdatedWrapper(0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401, true); @@ -82,14 +82,6 @@ contract CCIP2ETH is iCCIP2ETH { supportsInterface[iCallbackType.signedRedirect.selector] = true; } - /// Note - Checks for admin privileges - modifier OnlyDev() { - if (msg.sender != gateway.owner()) { - revert NotAuthorised("NOT_DEV"); - } - _; - } - /** * @dev Gets recordhash for a node * @param _node - Namehash of domain.eth, or bytes32(address _Owner) @@ -268,9 +260,6 @@ contract CCIP2ETH is iCCIP2ETH { } if (_recordhash.length == 0) { _recordhash = recordhash[bytes32(uint256(uint160(_owner)))]; - if (_recordhash.length == 0) { - _recordhash = abi.encodePacked("https://ccip.namesys.xyz"); // Web2 fallback - } } string memory _recType = gateway.funcToJson(request); // Filename for the requested record bytes32 _checkhash = @@ -319,7 +308,7 @@ contract CCIP2ETH is iCCIP2ETH { if (isWrapper[_owner]) { _owner = iToken(_owner).ownerOf(uint256(_node)); } - /// @dev - Timeout in 4 blocks (must be < 256 blocks) + /// @dev - Timeout in 4 blocks if (block.number > _blocknumber + 5) { revert InvalidRequest("BLOCK_TIMEOUT"); } @@ -382,7 +371,7 @@ contract CCIP2ETH is iCCIP2ETH { "Requesting Signature To Install dApp Service\n", "\nOrigin: ", _domain, // e.g. ens.domain.eth - "\ndApp: ", + "\nApp: ", _redirectDomain, // e.g. app.ens.eth "\nExtradata: 0x", gateway.bytesToHexString(abi.encodePacked(keccak256(result)), 0), @@ -568,12 +557,20 @@ contract CCIP2ETH is iCCIP2ETH { /// @dev : Management functions + /// @dev - Checks for admin privileges + modifier OnlyDev() { + if (msg.sender != gateway.owner()) { + revert NotAuthorised("NOT_DEV"); + } + _; + } + /// @dev - Returns owner of the contract function owner() public view returns (address) { return gateway.owner(); } - /// @dev - Updates ChainID in case of a hardfork + /// @dev - Updates ChainID in case of a hardfork function updateChainID() public { chainID = gateway.uintToString(block.chainid); } diff --git a/src/GatewayManager.sol b/src/GatewayManager.sol index 4503178..6194f76 100644 --- a/src/GatewayManager.sol +++ b/src/GatewayManager.sol @@ -12,14 +12,16 @@ import "./Interface.sol"; */ contract GatewayManager is iERC173, iGatewayManager { /// @dev - Events - event AddGateway(string indexed domain); - event RemoveGateway(string indexed domain); - event UpdateFuncFile(bytes4 _func, string _name); + event Web3GatewayUpdated(string indexed _domain); + event Web3GatewayRemoved(string indexed _domain); + event Web2GatewayUpdated(string indexed _domain); + event Web2GatewayRemoved(string indexed _domain); + event FuncMapUpdated(bytes4 _func, string _name); /// @dev - Errors error ContenthashNotImplemented(bytes1 _type); error InvalidRequest(string _message); - error UnimplementedFeature(bytes4 func); + error FeatureNotImplemented(bytes4 func); /// @dev - Contract owner/multisig address address public owner; @@ -33,8 +35,10 @@ contract GatewayManager is iERC173, iGatewayManager { address immutable THIS = address(this); /// @dev - Primary IPFS gateway domain, ipfs2.eth.limo string public PrimaryGateway = "ipfs2.eth.limo"; - /// @dev - List of secondary gateway domains - string[] public Gateways; + /// @dev - List of secondary gateway domains (default) + string[] public Web3Gateways; + /// @dev - List of web2 or L2 service gateway domains (fallback) + string[] public Web2Gateways; /// @dev - Resolver function bytes4 selector → Off-chain record filename .json mapping(bytes4 => string) public funcMap; @@ -49,10 +53,12 @@ contract GatewayManager is iERC173, iGatewayManager { funcMap[iResolver.contenthash.selector] = "contenthash"; funcMap[iResolver.zonehash.selector] = "dns/zonehash"; /// @dev - Set initial list of secondary gateways - Gateways.push("dweb.link"); - emit AddGateway("dweb.link"); - Gateways.push("ipfs.io"); - emit AddGateway("ipfs.io"); + Web3Gateways.push("dweb.link"); + emit Web3GatewayUpdated("dweb.link"); + Web3Gateways.push("ipfs.io"); + emit Web3GatewayUpdated("ipfs.io"); + Web2Gateways.push("ccip.namesys.xyz"); + emit Web2GatewayUpdated("ccip.namesys.xyz"); } /** @@ -68,21 +74,30 @@ contract GatewayManager is iERC173, iGatewayManager { view returns (string[] memory gateways) { - /// @dev Filter recordhash vs. web2 gateway - if (_recordhash.length == 32) { - // Short IPNS hash - _recordhash = abi.encodePacked(hex"e5010172002408011220", _recordhash); - } else if (iGatewayManager(this).isWeb2(_recordhash)) { - // Web2 fallback + /// @dev Filter IPNS versus Web2 gateway + if (_recordhash.length == 0) { + /// Default L2 or web2 Service fallback + uint256 gateLen = Web2Gateways.length; + gateways = new string[](gateLen); + while (gateLen > 0) { + --gateLen; + gateways[gateLen] = string.concat(string("https://"), Web2Gateways[gateLen], _path, ".json?t={data}"); + } + return gateways; + } else if (_recordhash[0] == bytes1("h") && iGatewayManager(this).isWeb2(_recordhash)) { + /// Trusted web2 gateway set by the owner gateways = new string[](1); gateways[0] = string.concat(string(_recordhash), _path, ".json?t={data}"); return gateways; + } else if (_recordhash.length == 32) { + /// Short IPNS hash set by owner + _recordhash = abi.encodePacked(hex"e5010172002408011220", _recordhash); } unchecked { - uint256 gLen = Gateways.length; + uint256 gLen = Web3Gateways.length; uint256 len = (gLen / 2) + 2; - if (len > 4) len = 4; - gateways = new string[](len); + if (len > 3) len = 3; + gateways = new string[](len + 1); uint256 i; if (bytes(PrimaryGateway).length > 0) { gateways[i++] = string.concat( @@ -116,8 +131,9 @@ contract GatewayManager is iERC173, iGatewayManager { } while (i < len) { seed = uint256(keccak256(abi.encodePacked(block.number * i, seed))); - gateways[i++] = string.concat("https://", Gateways[seed % gLen], _fullPath); + gateways[i++] = string.concat("https://", Web3Gateways[seed % gLen], _fullPath); } + gateways[len] = string.concat("https://", Web2Gateways[0], _fullPath); // Fallback } } @@ -128,7 +144,7 @@ contract GatewayManager is iERC173, iGatewayManager { this; response; extradata; - revert UnimplementedFeature(iGatewayManager.__fallback.selector); + revert FeatureNotImplemented(iGatewayManager.__fallback.selector); } /** @@ -170,7 +186,7 @@ contract GatewayManager is iERC173, iGatewayManager { } _jsonPath = string.concat("dns/", uintToString(resource)); } else { - revert UnimplementedFeature(func); + revert FeatureNotImplemented(func); } } @@ -281,46 +297,89 @@ contract GatewayManager is iERC173, iGatewayManager { */ function addFuncMap(bytes4 _func, string calldata _name) external onlyDev { funcMap[_func] = _name; - emit UpdateFuncFile(_func, _name); + emit FuncMapUpdated(_func, _name); + } + + /** + * @dev Shows list of web3 gateways + * @return - List of web3 gateways + */ + function listWeb3Gateways() external view returns (string[] memory) { + return Web3Gateways; } /** - * @dev Shows list of all available gateways - * @return list - List of gateways + * @dev Add a single web3 gateway + * @param _domain - New web3 gateway domain to add */ - function listGateways() external view returns (string[] memory list) { - return Gateways; + function addWeb3Gateway(string calldata _domain) external onlyDev { + Web3Gateways.push(_domain); + emit Web3GatewayUpdated(_domain); + } + + /** + * @dev Remove a single web3 gateway + * @param _index - Gateway index to remove + */ + function removeWeb3Gateway(uint256 _index) external onlyDev { + if (Web3Gateways.length == 1) { + revert InvalidRequest("LAST_GATEWAY"); + } + emit Web3GatewayRemoved(Web3Gateways[_index]); // Emit first + Web3Gateways[_index] = Web3Gateways[Web3Gateways.length - 1]; // Update later + Web3Gateways.pop(); + } + + /** + * @dev Replace a single web3 gateway + * @param _index : Gateway index to replace + * @param _domain : New gateway domain.tld + */ + function replaceWeb3Gateway(uint256 _index, string calldata _domain) external onlyDev { + emit Web3GatewayRemoved(Web3Gateways[_index]); // Emit first + Web3Gateways[_index] = _domain; // Update later + emit Web3GatewayUpdated(_domain); + } + + /** + * @dev Shows list of web2 gateways + * @return - List of web2 gateways + */ + function listWeb2Gateways() external view returns (string[] memory) { + return Web2Gateways; } /** * @dev Add a single gateway * @param _domain - New gateway domain to add */ - function addGateway(string calldata _domain) external onlyDev { - Gateways.push(_domain); - emit AddGateway(_domain); + function addWeb2Gateway(string calldata _domain) external onlyDev { + Web2Gateways.push(_domain); + emit Web2GatewayUpdated(_domain); } - /** - * @dev Remove a single gateway + * @dev Remove a single web2 gateway * @param _index - Gateway index to remove */ - function removeGateway(uint256 _index) external onlyDev { - require(Gateways.length > 1, "Last Gateway"); - emit RemoveGateway(Gateways[_index]); - Gateways[_index] = Gateways[Gateways.length - 1]; - Gateways.pop(); + + function removeWeb2Gateway(uint256 _index) external onlyDev { + if (Web2Gateways.length == 1) { + revert InvalidRequest("LAST_GATEWAY"); + } + emit Web2GatewayRemoved(Web2Gateways[_index]); + Web2Gateways[_index] = Web2Gateways[Web2Gateways.length - 1]; + Web2Gateways.pop(); } /** - * @dev Replace a single gateway + * @dev Replace a single web2 gateway * @param _index : Gateway index to replace * @param _domain : New gateway domain.tld */ - function replaceGateway(uint256 _index, string calldata _domain) external onlyDev { - emit RemoveGateway(Gateways[_index]); - Gateways[_index] = _domain; - emit AddGateway(_domain); + function replaceWeb2Gateway(uint256 _index, string calldata _domain) external onlyDev { + emit Web2GatewayRemoved(Web2Gateways[_index]); // Emit first + Web2Gateways[_index] = _domain; // Update later + emit Web2GatewayUpdated(_domain); } /** diff --git a/src/Interface.sol b/src/Interface.sol index 06d7bc1..0086d8e 100644 --- a/src/Interface.sol +++ b/src/Interface.sol @@ -58,16 +58,20 @@ interface iGatewayManager is iERC173 { function bytesToHexString(bytes calldata _buffer, uint256 _start) external pure returns (string memory); function bytes32ToHexString(bytes32 _buffer) external pure returns (string memory); function funcToJson(bytes calldata _request) external view returns (string memory _jsonPath); - function listGateways() external view returns (string[] memory list); function toChecksumAddress(address _addr) external pure returns (string memory); function __fallback(bytes calldata response, bytes calldata extradata) external view returns (bytes memory result); function addFuncMap(bytes4 _func, string calldata _name) external; - function addGateway(string calldata _domain) external; - function removeGateway(uint256 _index) external; - function replaceGateway(uint256 _index, string calldata _domain) external; + function listWeb2Gateways() external view returns (string[] memory list); + function addWeb2Gateway(string calldata _domain) external; + function removeWeb2Gateway(uint256 _index) external; + function replaceWeb2Gateway(uint256 _index, string calldata _domain) external; + function listWeb3Gateways() external view returns (string[] memory list); + function addWeb3Gateway(string calldata _domain) external; + function removeWeb3Gateway(uint256 _index) external; + function replaceWeb3Gateway(uint256 _index, string calldata _domain) external; function formatSubdomain(bytes calldata _recordhash) external pure returns (string memory result); function isWeb2(bytes calldata _recordhash) external pure returns (bool); } diff --git a/test/CCIP2ETH.t.sol b/test/CCIP2ETH.t.sol index af123df..a9a55e0 100644 --- a/test/CCIP2ETH.t.sol +++ b/test/CCIP2ETH.t.sol @@ -16,7 +16,6 @@ interface xENS is iENS { * Note Tests unwrapped/legacy domains */ contract CCIP2ETHTestLegacy is Test { - // using Surl for *; error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); address EOA = address(this); @@ -500,7 +499,6 @@ contract CCIP2ETHTestLegacy is Test { * Note Tests wrapped domains */ contract CCIP2ETHTestWrapped is Test { - // using Surl for *; error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); address EOA = address(this); diff --git a/test/GatewayManager.t.sol b/test/GatewayManager.t.sol index 08498ed..c7e8bfa 100644 --- a/test/GatewayManager.t.sol +++ b/test/GatewayManager.t.sol @@ -119,6 +119,137 @@ contract GatewayManagerTest is Test { bBytes = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; assertEq(bStr, gateway.bytesToHexString(bBytes, 0)); } + + /// @dev Test web2 gateways + function test6_Web2Gateways() public { + string[] memory gateways = gateway.listWeb2Gateways(); + assertEq("ccip.namesys.xyz", gateways[0]); + assertEq(1, gateways.length); + + vm.expectRevert(abi.encodeWithSelector(GatewayManager.InvalidRequest.selector, "LAST_GATEWAY")); + gateway.removeWeb2Gateway(0); + + gateway.addWeb2Gateway("ccip2.namesys.xyz"); + gateways = gateway.listWeb2Gateways(); + assertEq("ccip.namesys.xyz", gateways[0]); + assertEq("ccip2.namesys.xyz", gateways[1]); + assertEq(2, gateways.length); + + gateway.replaceWeb2Gateway(1, "ccipx.namesys.xyz"); + gateways = gateway.listWeb2Gateways(); + assertEq("ccip.namesys.xyz", gateways[0]); + assertEq("ccipx.namesys.xyz", gateways[1]); + assertEq(2, gateways.length); + + gateway.removeWeb2Gateway(0); + gateways = gateway.listWeb2Gateways(); + assertEq("ccipx.namesys.xyz", gateways[0]); + assertEq(1, gateways.length); + + gateway.addWeb2Gateway("ccip2.namesys.xyz"); + gateway.replaceWeb2Gateway(0, "ccip.namesys.xyz"); + gateway.removeWeb2Gateway(1); + gateways = gateway.listWeb2Gateways(); + assertEq("ccip.namesys.xyz", gateways[0]); + assertEq(1, gateways.length); + } + + /// @dev Test web3 gateways + function test7_Web3Gateways() public { + string[] memory gateways = gateway.listWeb3Gateways(); + assertEq("dweb.link", gateways[0]); + assertEq("ipfs.io", gateways[1]); + assertEq(2, gateways.length); + + gateway.removeWeb3Gateway(0); + vm.expectRevert(abi.encodeWithSelector(GatewayManager.InvalidRequest.selector, "LAST_GATEWAY")); + gateway.removeWeb3Gateway(0); + + gateway.addWeb3Gateway("dweb.link"); + gateways = gateway.listWeb3Gateways(); + assertEq("dweb.link", gateways[1]); + assertEq("ipfs.io", gateways[0]); + assertEq(2, gateways.length); + + gateway.replaceWeb3Gateway(0, "ipfs.namesys.xyz"); + gateways = gateway.listWeb3Gateways(); + assertEq("dweb.link", gateways[1]); + assertEq("ipfs.namesys.xyz", gateways[0]); + assertEq(2, gateways.length); + + gateway.removeWeb3Gateway(0); + gateways = gateway.listWeb3Gateways(); + assertEq("dweb.link", gateways[0]); + assertEq(1, gateways.length); + + gateway.addWeb3Gateway("ipfs.namesys.xyz"); + gateway.replaceWeb3Gateway(0, "ipfs.io"); + gateway.removeWeb3Gateway(0); + gateways = gateway.listWeb3Gateways(); + assertEq("ipfs.namesys.xyz", gateways[0]); + assertEq(1, gateways.length); + } + + /// @dev Test a gateway + function test8_RandomGateway() public { + string memory _path = "/.well-known/eth/freetib/contenthash"; + bytes memory _recordhash; + string[] memory gateways = gateway.randomGateways(_recordhash, _path, 0); + assertEq(gateways[0], "https://ccip.namesys.xyz/.well-known/eth/freetib/contenthash.json?t={data}"); + assertEq(1, gateways.length); + gateway.addWeb2Gateway("ccip2.namesys.xyz"); + gateways = gateway.randomGateways(_recordhash, _path, 0); + assertEq(2, gateways.length); + assertEq(gateways[0], "https://ccip.namesys.xyz/.well-known/eth/freetib/contenthash.json?t={data}"); + assertEq(gateways[1], "https://ccip2.namesys.xyz/.well-known/eth/freetib/contenthash.json?t={data}"); + + _recordhash = bytes("https://ccip.namesys.xyz"); + gateways = gateway.randomGateways(_recordhash, _path, 0); + assertEq(1, gateways.length); + assertEq(gateways[0], "https://ccip.namesys.xyz/.well-known/eth/freetib/contenthash.json?t={data}"); + + gateway.addWeb3Gateway("ipfs.namesys.xyz"); + gateway.addWeb3Gateway("ipfs2.namesys.xyz"); + _recordhash = hex"3c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; + gateways = gateway.randomGateways(_recordhash, _path, 0); + assertEq(4, gateways.length); + assertEq( + gateways[0], + "https://e5010172002408011220.3c5aba6c9b5055a5fa12281c486188ed.8ae2b6ef394b3d981b00d17a4b51735c.ipfs2.eth.limo/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[1], + "https://dweb.link/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[2], + "https://ipfs.namesys.xyz/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[3], + "https://ccip.namesys.xyz/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + + _recordhash = hex"e50101720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; + gateways = gateway.randomGateways(_recordhash, _path, 0); + assertEq(4, gateways.length); + assertEq( + gateways[0], + "https://e5010172002408011220.3c5aba6c9b5055a5fa12281c486188ed.8ae2b6ef394b3d981b00d17a4b51735c.ipfs2.eth.limo/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[1], + "https://dweb.link/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[2], + "https://ipfs.namesys.xyz/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + assertEq( + gateways[3], + "https://ccip.namesys.xyz/ipns/f01720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c/.well-known/eth/freetib/contenthash.json?t={data}" + ); + } } /// @dev Utility functions