-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7249900
commit e39e7fe
Showing
8 changed files
with
317 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ | ||
ens-contracts/=lib/ens-contracts/contracts/ | ||
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ | ||
forge-std/=lib/forge-std/src/ | ||
halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/ | ||
openzeppelin-contracts/=lib/openzeppelin-contracts/ |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.13; | ||
|
||
import {Script, console} from "forge-std/Script.sol"; | ||
import {HybridResolver} from "../src/IPCMResolver.sol"; | ||
import {ENS} from "../lib/ens-contracts/contracts/registry/ENS.sol"; | ||
import {INameWrapper} from "../lib/ens-contracts/contracts/wrapper/INameWrapper.sol"; | ||
|
||
contract DeployResolver is Script { | ||
HybridResolver public resolver; | ||
|
||
function setUp() public {} | ||
|
||
function run() public { | ||
vm.startBroadcast(); | ||
|
||
ENS ens = ENS(vm.envAddress("ENS_REGISTRY")); | ||
INameWrapper nameWrapper = INameWrapper(vm.envAddress("NAME_WRAPPER")); | ||
string memory url = vm.envString("GATEWAY_URL"); | ||
address signer = vm.envAddress("SIGNER"); | ||
address owner = vm.envAddress("OWNER"); | ||
|
||
resolver = new HybridResolver(ens, nameWrapper, url, signer, owner); | ||
|
||
vm.stopBroadcast(); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "../lib/ens-contracts/contracts/registry/ENS.sol"; | ||
import "../lib/ens-contracts/contracts/wrapper/INameWrapper.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/IExtendedResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/ABIResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/AddrResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/ContentHashResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/NameResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/TextResolver.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/Multicallable.sol"; | ||
import "../lib/ens-contracts/contracts/resolvers/profiles/InterfaceResolver.sol"; | ||
import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; | ||
|
||
import "./SignatureVerifier.sol"; | ||
|
||
interface IResolverService { | ||
function resolve( | ||
bytes calldata name, | ||
bytes calldata data | ||
) | ||
external | ||
view | ||
returns (bytes memory result, uint64 expires, bytes memory sig); | ||
} | ||
|
||
/** | ||
* A hybrid onchain/offchain ENS resolver contract for flexible record management. | ||
*/ | ||
contract HybridResolver is | ||
Multicallable, | ||
ABIResolver, | ||
AddrResolver, | ||
ContentHashResolver, | ||
InterfaceResolver, | ||
NameResolver, | ||
TextResolver, | ||
IExtendedResolver, | ||
Ownable | ||
{ | ||
ENS immutable ens; | ||
INameWrapper immutable nameWrapper; | ||
string public url; | ||
address public signer; | ||
|
||
error OffchainLookup( | ||
address sender, | ||
string[] urls, | ||
bytes callData, | ||
bytes4 callbackFunction, | ||
bytes extraData | ||
); | ||
|
||
constructor( | ||
ENS _ens, | ||
INameWrapper _nameWrapper, | ||
string memory _url, | ||
address _signer, | ||
address _owner | ||
) Ownable(_owner) { | ||
ens = _ens; | ||
nameWrapper = _nameWrapper; | ||
url = _url; | ||
signer = _signer; | ||
} | ||
|
||
function resolve( | ||
bytes calldata name, | ||
bytes memory data | ||
) external view virtual returns (bytes memory) { | ||
(bool success, bytes memory result) = address(this).staticcall(data); | ||
bytes32 hashedResult = keccak256(result); | ||
|
||
// keccak256(0x0000000000000000000000000000000000000000000000000000000000000000) | ||
// covers empty addr(node), name(node), contenthash(node) | ||
bytes32 emptySingleArg = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; | ||
|
||
// keccak256(0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000) | ||
// covers addr(node, coinType), text(node, key), ABI(node, contentTypes) | ||
bytes32 emptyDoubleArg = 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; | ||
|
||
if ( | ||
success && | ||
(hashedResult != emptySingleArg) && | ||
(hashedResult != emptyDoubleArg) | ||
) { | ||
// If we have an onchain result, return it | ||
return result; | ||
} else { | ||
// Otherwise, fallback to offchain lookup | ||
return resolveOffchain(name, data); | ||
} | ||
} | ||
|
||
function resolveOffchain( | ||
bytes calldata name, | ||
bytes memory data | ||
) internal view virtual returns (bytes memory) { | ||
bytes memory callData = abi.encodeWithSelector( | ||
IResolverService.resolve.selector, | ||
name, | ||
data | ||
); | ||
string[] memory urls = new string[](1); | ||
urls[0] = url; | ||
revert OffchainLookup( | ||
address(this), | ||
urls, | ||
callData, | ||
HybridResolver.resolveWithProof.selector, | ||
abi.encode(callData, address(this)) | ||
); | ||
} | ||
|
||
function resolveWithProof( | ||
bytes calldata response, | ||
bytes calldata extraData | ||
) external view returns (bytes memory) { | ||
(address _signer, bytes memory result) = SignatureVerifier.verify( | ||
extraData, | ||
response | ||
); | ||
require(_signer == signer, "SignatureVerifier: Invalid sigature"); | ||
return result; | ||
} | ||
|
||
function supportsInterface( | ||
bytes4 interfaceID | ||
) | ||
public | ||
view | ||
override( | ||
Multicallable, | ||
ABIResolver, | ||
AddrResolver, | ||
ContentHashResolver, | ||
InterfaceResolver, | ||
NameResolver, | ||
TextResolver | ||
) | ||
returns (bool) | ||
{ | ||
return | ||
interfaceID == type(IExtendedResolver).interfaceId || | ||
super.supportsInterface(interfaceID); | ||
} | ||
|
||
function isAuthorised(bytes32 node) internal view override returns (bool) { | ||
address owner = ens.owner(node); | ||
|
||
if ( | ||
owner == address(nameWrapper) | ||
? !nameWrapper.canModifyName(node, msg.sender) | ||
: (owner != msg.sender && | ||
!ens.isApprovedForAll(owner, msg.sender)) | ||
) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function setUrl(string memory _url) external onlyOwner { | ||
url = _url; | ||
} | ||
|
||
function setSigner(address _signer) external onlyOwner { | ||
signer = _signer; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "../lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; | ||
|
||
library SignatureVerifier { | ||
/** | ||
* @dev Generates a hash for signing/verifying. | ||
* @param target: The address the signature is for. | ||
* @param request: The original request that was sent. | ||
* @param result: The `result` field of the response (not including the signature part). | ||
*/ | ||
function makeSignatureHash( | ||
address target, | ||
uint64 expires, | ||
bytes memory request, | ||
bytes memory result | ||
) internal pure returns (bytes32) { | ||
return | ||
keccak256( | ||
abi.encodePacked( | ||
hex"1900", | ||
target, | ||
expires, | ||
keccak256(request), | ||
keccak256(result) | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* @dev Verifies a signed message returned from a callback. | ||
* @param request: The original request that was sent. | ||
* @param response: An ABI encoded tuple of `(bytes result, uint64 expires, bytes sig)`, where `result` is the data to return | ||
* to the caller, and `sig` is the (r,s,v) encoded message signature. | ||
* @return signer: The address that signed this message. | ||
* @return result: The `result` decoded from `response`. | ||
*/ | ||
function verify( | ||
bytes calldata request, | ||
bytes calldata response | ||
) internal view returns (address, bytes memory) { | ||
(bytes memory result, uint64 expires, bytes memory sig) = abi.decode( | ||
response, | ||
(bytes, uint64, bytes) | ||
); | ||
(bytes memory extraData, address sender) = abi.decode( | ||
request, | ||
(bytes, address) | ||
); | ||
address signer = ECDSA.recover( | ||
makeSignatureHash(sender, expires, extraData, result), | ||
sig | ||
); | ||
require( | ||
expires >= block.timestamp, | ||
"SignatureVerifier: Signature expired" | ||
); | ||
return (signer, result); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.13; | ||
|
||
import {Test, console} from "forge-std/Test.sol"; | ||
import {HybridResolver} from "../src/IPCMResolver.sol"; | ||
import "../lib/ens-contracts/contracts/registry/ENS.sol"; | ||
import "../lib/ens-contracts/contracts/wrapper/INameWrapper.sol"; | ||
import "../lib/ens-contracts/contracts/registry/ENSRegistry.sol"; | ||
|
||
contract IPCMResolverTest is Test { | ||
HybridResolver public resolver; | ||
ENS public ens; | ||
INameWrapper public nameWrapper; | ||
|
||
address signer = address(0x123); | ||
address owner = address(0x456); | ||
string url = "https://example.com"; | ||
|
||
function setUp() public { | ||
ens = new ENSRegistry(); // Use concrete implementation instead of interface | ||
nameWrapper = INameWrapper(address(0x789)); // Mock wrapper | ||
resolver = new HybridResolver(ens, nameWrapper, url, signer, owner); | ||
} | ||
|
||
function testSetUrl() public { | ||
string memory newUrl = "https://newexample.com"; | ||
vm.prank(owner); | ||
resolver.setUrl(newUrl); | ||
assertEq(resolver.url(), newUrl); | ||
} | ||
|
||
function testSetSigner() public { | ||
address newSigner = address(0xabc); | ||
vm.prank(owner); | ||
resolver.setSigner(newSigner); | ||
assertEq(resolver.signer(), newSigner); | ||
} | ||
|
||
// function testOnlyOwnerCanSetUrl() public { | ||
// string memory newUrl = "https://newexample.com"; | ||
// vm.prank(address(0xdef)); | ||
// vm.expectRevert("Ownable: caller is not the owner"); | ||
// resolver.setUrl(newUrl); | ||
// } | ||
|
||
// function testOnlyOwnerCanSetSigner() public { | ||
// address newSigner = address(0xabc); | ||
// vm.prank(address(0xdef)); | ||
// vm.expectRevert("Ownable: caller is not the owner"); | ||
// resolver.setSigner(newSigner); | ||
// } | ||
} |