Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = release-v4.1
[submodule "lib/buffer"]
path = lib/buffer
url = https://github.com/ensdomains/buffer
[submodule "lib/solsha1"]
path = lib/solsha1
url = https://github.com/ensdomains/solsha1
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ build
forge-cache
out
*.sh
deployments
deployments
lib/
3 changes: 1 addition & 2 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
"bracketSpacing": false
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,14 @@ This repo runs a husky precommit to prettify all contract files to keep them con
git clone https://github.com/ensdomains/ens-contracts
cd ens-contracts
bun i
forge i
```

### How to run tests

```
bun run test
forge test
```

### How to publish
Expand Down
Binary file added bun.lockb
Binary file not shown.
113 changes: 113 additions & 0 deletions contracts/ccipRead/CCIPReader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// source: https://github.com/unruggable-labs/CCIPReader.sol/

/*
MIT License

Copyright (c) 2025 Unruggable

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

import {OffchainLookup, OffchainLookupTuple, EIP3668} from "./EIP3668.sol";

/// A standardised Carry definition for use in the context of our `CCIPReader` architecture
struct Carry {
address target;
bytes4 callback;
bytes carry;
bytes4 myCallback;
bytes myCarry;
}

contract CCIPReader {
/**
* @notice A function that wraps, handles, and consistently returns responses from calls to a function within a target contract that can return directly OR return in response to offchain data resolution (subject to the ERC-3668 specification).
* @param target - the real contract where OUR execution will occur
* @param call - the calldata that we want to execute against that target
* @param mySelector - the 4 bytes selector of OUR callback exit point
* @param myCarry - encoded bytes data that we want to pass through to our exit point
*/
function ccipRead(
address target,
bytes memory call,
bytes4 mySelector,
bytes memory myCarry
) internal view returns (bytes memory v) {
/// We call the intended function that **could** revert with an `OffchainLookup`
/// We destructure the response into an execution status bool and our return bytes
bool ok;
(ok, v) = target.staticcall(call);
/// IF the function reverted with an `OffchainLookup`
if (!ok && bytes4(v) == OffchainLookup.selector) {
/// We decode the response error into a tuple
/// tuples allow flexibility noting stack too deep constraints
OffchainLookupTuple memory x = EIP3668.decode(v);
if (x.sender == target) {
/// We then wrap the error data in an `OffchainLookup` sent/'owned' by this contract
revert OffchainLookup(
address(this),
x.gateways,
x.request,
this.ccipReadCallback.selector,
abi.encode(
Carry(target, x.selector, x.carry, mySelector, myCarry)
)
);
}
}

/// IF we have gotten here, the 'real' target does not revert with an `OffchainLookup` error
if (ok) {
/// The exit point of this architecture is OUR callback in the 'real'
/// We pass through the response to that callback
(ok, v) = address(this).staticcall(
abi.encodeWithSelector(mySelector, v, myCarry)
);
}

/// OR the call to the 'real' target reverts with a different error selector
/// OR the call to OUR callback reverts with ANY error selector
if (!ok) {
assembly {
revert(add(v, 32), mload(v))
}
}
}

function ccipReadCallback(
bytes memory ccip,
bytes memory carry
) external view {
Carry memory state = abi.decode(carry, (Carry));
/// Since the callback can revert too (but has the same return structure)
/// We can reuse the calling infrastructure to call the callback
bytes memory v = ccipRead(
state.target,
abi.encodeWithSelector(state.callback, ccip, state.carry),
state.myCallback,
state.myCarry
);
assembly {
return(add(v, 32), mload(v))
}
}
}
71 changes: 71 additions & 0 deletions contracts/ccipRead/EIP3668.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// source: https://github.com/unruggable-labs/CCIPReader.sol/

/*
MIT License

Copyright (c) 2025 Unruggable

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// https://eips.ethereum.org/EIPS/eip-3668
error OffchainLookup(
address sender,
string[] urls,
bytes request,
bytes4 callback,
bytes carry
);

struct OffchainLookupTuple {
address sender;
string[] gateways;
bytes request;
bytes4 selector;
bytes carry;
}

library EIP3668 {
/// This function decodes an `OffchainLookup` error into the properties of an `OffchainLookupTuple`
/// This is essentially a conversion from an error to a struct
function decode(
bytes memory v
) internal pure returns (OffchainLookupTuple memory x) {
(x.sender, x.gateways, x.request, x.selector, x.carry) = abi.decode(
drop(v, 4),
(address, string[], bytes, bytes4, bytes)
);
}

/// @dev Drop leading bytes
function drop(
bytes memory data,
uint256 size
) internal pure returns (bytes memory dropped) {
require(data.length >= size);
dropped = abi.encodePacked(data); // make a copy
assembly {
mstore(add(dropped, size), sub(mload(dropped), size)) // subtract length
dropped := add(dropped, size) // add offset
}
}
}
7 changes: 7 additions & 0 deletions contracts/resolvers/IResolveMulticall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @dev EIP-3668 encoding for resolve(multicall)
interface IResolveMulticall {
function multicall(bytes[] calldata) external view returns (bytes[] memory);
}
10 changes: 10 additions & 0 deletions contracts/resolvers/profiles/IExtendedResolver.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
* @notice No resolver is set for `name` according to ENSIP-10
*/
error UnreachableName(bytes name);

/**
* @notice The resolver does not implement `selector`
*/
error UnsupportedResolverProfile(bytes4 selector);

interface IExtendedResolver {
function resolve(
bytes memory name,
Expand Down
4 changes: 1 addition & 3 deletions contracts/reverseRegistrar/ReverseRegistrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar {
assembly {
for {
let i := 40
} gt(i, 0) {

} {
} gt(i, 0) {} {
i := sub(i, 1)
mstore8(i, byte(and(addr, 0xf), lookup))
addr := div(addr, 0x10)
Expand Down
Loading
Loading