Skip to content

Commit

Permalink
chore: Refactored to check public resolvers first
Browse files Browse the repository at this point in the history
  • Loading branch information
stevedylandev committed Feb 4, 2025
1 parent e95070f commit 3047413
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 105 deletions.
8 changes: 5 additions & 3 deletions script/IPCMResolver.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {IPCMResolver} from "../src/IPCMResolver.sol";
Expand All @@ -19,8 +19,10 @@ contract DeployResolver is Script {
string memory url = vm.envString("GATEWAY_URL");
address signer = vm.envAddress("SIGNER");
address owner = vm.envAddress("OWNER");
address publicResolver = vm.envAddress("PUBLIC_RESOLVER");
address legacyResolver = vm.envAddress("LEGACY_RESOLVER");

resolver = new IPCMResolver(ens, nameWrapper, url, signer, owner);
resolver = new IPCMResolver(ens, nameWrapper, url, signer, owner, publicResolver, legacyResolver);

vm.stopBroadcast();
}
Expand Down
102 changes: 39 additions & 63 deletions src/IPCMResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import "./SignatureVerifier.sol";

interface IResolverService {
function resolve(
bytes calldata name,
bytes calldata data
)
function resolve(bytes calldata name, bytes calldata data)
external
view
returns (bytes memory result, uint64 expires, bytes memory sig);
Expand All @@ -43,33 +40,47 @@ contract IPCMResolver is
INameWrapper immutable nameWrapper;
string public url;
address public signer;
address public publicResolver;
address public legacyResolver;

error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);
error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData);

constructor(
ENS _ens,
INameWrapper _nameWrapper,
string memory _url,
address _signer,
address _owner
address _owner,
address _publicResolver,
address _legacyResolver
) Ownable(_owner) {
ens = _ens;
nameWrapper = _nameWrapper;
url = _url;
signer = _signer;
publicResolver = _publicResolver;
legacyResolver = _legacyResolver;
}

function resolve(
bytes calldata name,
bytes memory data
) external view virtual returns (bytes memory) {
(bool success, bytes memory result) = address(this).staticcall(data);
function resolve(bytes calldata name, bytes memory data) external view virtual returns (bytes memory) {
// If we have an onchain result in this contract, return it
bytes memory internalResult = resolveOnchain(address(this), data);
if (internalResult.length > 0) return internalResult;

// If we have an onchain result in the latest public resolver, return it
bytes memory publicResResult = resolveOnchain(publicResolver, data);
if (publicResResult.length > 0) return publicResResult;

// If we have an onchain result in the legacy public resolver, return it
bytes memory legacyResResult = resolveOnchain(legacyResolver, data);
if (legacyResResult.length > 0) return legacyResResult;

// Otherwise, fallback to offchain lookup
return resolveOffchain(name, data);
}

function resolveOnchain(address resolver, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory result) = resolver.staticcall(data);
bytes32 hashedResult = keccak256(result);

// keccak256(0x0000000000000000000000000000000000000000000000000000000000000000)
Expand All @@ -80,70 +91,36 @@ contract IPCMResolver is
// 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
if (success && (hashedResult != emptySingleArg) && (hashedResult != emptyDoubleArg)) {
return result;
} else {
// Otherwise, fallback to offchain lookup
return resolveOffchain(name, data);
}
return bytes("");
}

function resolveOffchain(
bytes calldata name,
bytes memory data
) internal view virtual returns (bytes memory) {
bytes memory callData = abi.encodeWithSelector(
IResolverService.resolve.selector,
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,
IPCMResolver.resolveWithProof.selector,
abi.encode(callData, address(this))
address(this), urls, callData, IPCMResolver.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
);
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
)
function supportsInterface(bytes4 interfaceID)
public
view
override(
Multicallable,
ABIResolver,
AddrResolver,
ContentHashResolver,
InterfaceResolver,
NameResolver,
TextResolver
Multicallable, ABIResolver, AddrResolver, ContentHashResolver, InterfaceResolver, NameResolver, TextResolver
)
returns (bool)
{
return
interfaceID == type(IExtendedResolver).interfaceId ||
super.supportsInterface(interfaceID);
return interfaceID == type(IExtendedResolver).interfaceId || super.supportsInterface(interfaceID);
}

function isAuthorised(bytes32 node) internal view override returns (bool) {
Expand All @@ -152,8 +129,7 @@ contract IPCMResolver is
if (
owner == address(nameWrapper)
? !nameWrapper.canModifyName(node, msg.sender)
: (owner != msg.sender &&
!ens.isApprovedForAll(owner, msg.sender))
: (owner != msg.sender && !ens.isApprovedForAll(owner, msg.sender))
) {
return false;
}
Expand Down
47 changes: 11 additions & 36 deletions src/SignatureVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,12 @@ library SignatureVerifier {
* @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)
)
);
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)));
}

/**
Expand All @@ -36,26 +26,11 @@ library SignatureVerifier {
* @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"
);
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);
}
}
8 changes: 5 additions & 3 deletions test/IPCMResolver.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {IPCMResolver} from "../src/IPCMResolver.sol";
Expand All @@ -15,11 +15,13 @@ contract IPCMResolverTest is Test {
address signer = address(0x123);
address owner = address(0x456);
string url = "https://example.com";
address publicResolver = address(0x321);
address legacyResolver = address(0x654);

function setUp() public {
ens = new ENSRegistry(); // Use concrete implementation instead of interface
nameWrapper = INameWrapper(address(0x789)); // Mock wrapper
resolver = new IPCMResolver(ens, nameWrapper, url, signer, owner);
resolver = new IPCMResolver(ens, nameWrapper, url, signer, owner, publicResolver, legacyResolver);
}

function testSetUrl() public {
Expand Down

0 comments on commit 3047413

Please sign in to comment.