diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..d6daa6b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + name: Contract tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: "recursive" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Use Bun 1.1.16 + uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.1.16 + + - run: bun install --frozen-lockfile + + - name: Run tests + run: bun --filter contracts test diff --git a/bun.lockb b/bun.lockb index 0705dcc..949336f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/contracts/hardhat.config.cts b/contracts/hardhat.config.cts new file mode 100644 index 0000000..e2b1660 --- /dev/null +++ b/contracts/hardhat.config.cts @@ -0,0 +1,19 @@ +import type { HardhatUserConfig } from "hardhat/config"; + +import "@nomicfoundation/hardhat-foundry"; +import "@nomicfoundation/hardhat-verify"; +import "@nomicfoundation/hardhat-viem"; +import "./tasks/esm_fix.cjs"; + +import("@ensdomains/hardhat-chai-matchers-viem"); + +const config = { + solidity: { + version: "0.8.25", + settings: { + evmVersion: "cancun", + }, + }, +} satisfies HardhatUserConfig; + +export default config; diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts deleted file mode 100644 index f81f07f..0000000 --- a/contracts/hardhat.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import "@nomicfoundation/hardhat-foundry"; -import "@nomicfoundation/hardhat-toolbox"; -import "@nomicfoundation/hardhat-viem"; -import type { HardhatUserConfig } from "hardhat/config"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.25", - settings: { - evmVersion: "cancun" - } - } -}; - -export default config; diff --git a/contracts/package.json b/contracts/package.json index 1abed01..314b68e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,17 +1,22 @@ { "name": "contracts", - "module": "index.ts", + "type": "module", "scripts": { - "test:hardhat": "hardhat test", - "test:forge": "echo \"Skipping forge test for now since there are no tests...\"", + "compile:hardhat": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only' hardhat compile", + "test:hardhat": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only' hardhat test", + "test:forge": "forge test", "test": "bun run test:forge && bun run test:hardhat" }, "devDependencies": { + "@ensdomains/hardhat-chai-matchers-viem": "^0.0.7", "@nomicfoundation/hardhat-foundry": "^1.1.1", - "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "@nomicfoundation/hardhat-toolbox-viem": "^3.0.0", "@nomicfoundation/hardhat-viem": "^2.0.0", "@types/bun": "latest", + "@vitest/expect": "^1.6.0", + "chai": "^5.1.1", "hardhat": "^2.22.2", + "ts-node": "^10.9.2", "viem": "^2.9.12" }, "peerDependencies": { diff --git a/contracts/src/registry/BaseRegistry.sol b/contracts/src/registry/BaseRegistry.sol index 761d559..aa904de 100644 --- a/contracts/src/registry/BaseRegistry.sol +++ b/contracts/src/registry/BaseRegistry.sol @@ -14,9 +14,9 @@ import {IRegistryDatastore} from "./IRegistryDatastore.sol"; import {IRegistry} from "./IRegistry.sol"; abstract contract BaseRegistry is IRegistry, ERC1155Singleton { - error AccessDenied(string label, address owner, address caller); - error InvalidSubregistryFlags(string label, uint96 flags, uint96 expected); - error InvalidResolverFlags(string label, uint96 flags, uint96 expected); + error AccessDenied(uint256 tokenId, address owner, address caller); + error InvalidSubregistryFlags(uint256 tokenId, uint96 flags, uint96 expected); + error InvalidResolverFlags(uint256 tokenId, uint96 flags, uint96 expected); IRegistryDatastore public datastore; @@ -24,27 +24,26 @@ abstract contract BaseRegistry is IRegistry, ERC1155Singleton { datastore = _datastore; } - modifier onlyTokenOwner(string calldata label) { - uint256 tokenId = uint256(keccak256(bytes(label))); + modifier onlyTokenOwner(uint256 tokenId) { address owner = ownerOf(tokenId); if (owner != msg.sender) { - revert AccessDenied(label, owner, msg.sender); + revert AccessDenied(tokenId, owner, msg.sender); } _; } - modifier withSubregistryFlags(string calldata label, uint96 mask, uint96 expected) { - (, uint96 flags) = datastore.getSubregistry(uint256(keccak256(bytes(label)))); + modifier withSubregistryFlags(uint256 tokenId, uint96 mask, uint96 expected) { + (, uint96 flags) = datastore.getSubregistry(tokenId); if (flags & mask != expected) { - revert InvalidSubregistryFlags(label, flags & mask, expected); + revert InvalidSubregistryFlags(tokenId, flags & mask, expected); } _; } - modifier withResolverFlags(string calldata label, uint96 mask, uint96 expected) { - (, uint96 flags) = datastore.getResolver(uint256(keccak256(bytes(label)))); + modifier withResolverFlags(uint256 tokenId, uint96 mask, uint96 expected) { + (, uint96 flags) = datastore.getResolver(tokenId); if (flags & mask != expected) { - revert InvalidResolverFlags(label, flags & mask, expected); + revert InvalidResolverFlags(tokenId, flags & mask, expected); } _; } @@ -52,31 +51,34 @@ abstract contract BaseRegistry is IRegistry, ERC1155Singleton { /** * @dev See {IERC165-supportsInterface}. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Singleton, IERC165) returns (bool) { - return - interfaceId == type(IERC1155).interfaceId || - interfaceId == type(IERC1155MetadataURI).interfaceId || - interfaceId == type(IRegistry).interfaceId || - super.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC1155Singleton, IERC165) + returns (bool) + { + return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC1155MetadataURI).interfaceId + || interfaceId == type(IRegistry).interfaceId || super.supportsInterface(interfaceId); } - function _mint(string calldata label, address owner, IRegistry registry, uint96 flags) internal { - uint256 tokenId = uint256(keccak256(bytes(label))); + function _mint(uint256 tokenId, address owner, IRegistry registry, uint96 flags) internal { _mint(owner, tokenId, 1, ""); datastore.setSubregistry(tokenId, address(registry), flags); - emit NewSubname(label); } - /*********************** + /** + * * IRegistry functions * - ***********************/ - + * + */ + /** * @dev Fetches the registry for a subdomain of the current registry. * @param label The label to resolve. * @return The address of the registry for this subdomain, or `address(0)` if none exists. */ - function getSubregistry(string calldata label) external virtual view returns (IRegistry) { + function getSubregistry(string calldata label) external view virtual returns (IRegistry) { (address subregistry,) = datastore.getSubregistry(uint256(keccak256(bytes(label)))); return IRegistry(subregistry); } @@ -86,7 +88,7 @@ abstract contract BaseRegistry is IRegistry, ERC1155Singleton { * @param label The label to fetch a resolver for. * @return resolver The address of a resolver responsible for this name, or `address(0)` if none exists. */ - function getResolver(string calldata label) external virtual view returns (address resolver) { + function getResolver(string calldata label) external view virtual returns (address resolver) { (resolver,) = datastore.getResolver(uint256(keccak256(bytes(label)))); } } diff --git a/contracts/src/registry/ERC1155Singleton.sol b/contracts/src/registry/ERC1155Singleton.sol index 33824e0..821f1b5 100644 --- a/contracts/src/registry/ERC1155Singleton.sol +++ b/contracts/src/registry/ERC1155Singleton.sol @@ -19,12 +19,8 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 using Arrays for uint256[]; using Arrays for address[]; - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + mapping(uint256 id => address) private _owners; mapping(address account => mapping(address operator => bool)) private _operatorApprovals; @@ -37,18 +33,16 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC1155).interfaceId || - interfaceId == type(IERC1155Singleton).interfaceId || - interfaceId == type(IERC1155MetadataURI).interfaceId || - super.supportsInterface(interfaceId); + return interfaceId == type(IERC1155).interfaceId || interfaceId == type(IERC1155Singleton).interfaceId + || interfaceId == type(IERC1155MetadataURI).interfaceId || super.supportsInterface(interfaceId); } - /************************************************************************** + /** + * * ERC1155 methods - *************************************************************************/ - - function uri(uint256 /* id */) public view virtual returns (string memory); + * + */ + function uri(uint256 /* id */ ) public view virtual returns (string memory); /** * @dev See {IERC1155-balanceOf}. @@ -64,10 +58,12 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 * * - `accounts` and `ids` must have the same length. */ - function balanceOfBatch( - address[] memory accounts, - uint256[] memory ids - ) public view virtual returns (uint256[] memory) { + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public + view + virtual + returns (uint256[] memory) + { if (accounts.length != ids.length) { revert ERC1155InvalidArrayLength(ids.length, accounts.length); } @@ -148,11 +144,11 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 uint256 id = ids.unsafeMemoryAccess(i); uint256 value = values.unsafeMemoryAccess(i); - if(value > 0) { + if (value > 0) { address owner = _owners[id]; - if(owner != from) { + if (owner != from) { revert ERC1155InsufficientBalance(from, 0, value, id); - } else if(value > 1) { + } else if (value > 1) { revert ERC1155InsufficientBalance(from, 1, value, id); } _owners[id] = to; @@ -320,7 +316,7 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 } _updateWithAcceptanceCheck(from, address(0), ids, values, ""); } - + /** * @dev Approve `operator` to operate on all of `owner` tokens * @@ -341,10 +337,11 @@ abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC11 /** * @dev Creates an array in memory with only one value for each of the elements provided. */ - function _asSingletonArrays( - uint256 element1, - uint256 element2 - ) private pure returns (uint256[] memory array1, uint256[] memory array2) { + function _asSingletonArrays(uint256 element1, uint256 element2) + private + pure + returns (uint256[] memory array1, uint256[] memory array2) + { /// @solidity memory-safe-assembly assembly { // Load the free memory pointer diff --git a/contracts/src/registry/ETHRegistry.sol b/contracts/src/registry/ETHRegistry.sol index e6422ed..17b897d 100644 --- a/contracts/src/registry/ETHRegistry.sol +++ b/contracts/src/registry/ETHRegistry.sol @@ -11,26 +11,30 @@ import {BaseRegistry} from "./BaseRegistry.sol"; contract ETHRegistry is BaseRegistry, AccessControl { bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); - uint96 public constant SUBREGISTRY_FLAGS_MASK = 0x100000000; - uint96 public constant SUBREGISTRY_FLAG_LOCKED = 0x100000000; + uint32 public constant FLAGS_MASK = 0x7; + uint32 public constant FLAG_SUBREGISTRY_LOCKED = 0x1; + uint32 public constant FLAG_RESOLVER_LOCKED = 0x2; + uint32 public constant FLAG_FLAGS_LOCKED = 0x4; error NameAlreadyRegistered(string label); - error NameExpired(string label); + error NameExpired(uint256 tokenId); error CannotReduceExpiration(uint64 oldExpiration, uint64 newExpiration); - constructor(IRegistryDatastore _datastore) - BaseRegistry(_datastore) - { + constructor(IRegistryDatastore _datastore) BaseRegistry(_datastore) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function uri(uint256 /*tokenId*/) public override pure returns(string memory) { + function uri(uint256 /*tokenId*/ ) public pure override returns (string memory) { return ""; } - function ownerOf( - uint256 tokenId - ) public view virtual override(ERC1155Singleton, IERC1155Singleton) returns (address) { + function ownerOf(uint256 tokenId) + public + view + virtual + override(ERC1155Singleton, IERC1155Singleton) + returns (address) + { (, uint96 flags) = datastore.getSubregistry(tokenId); uint64 expires = uint64(flags); if (expires < block.timestamp) { @@ -39,75 +43,83 @@ contract ETHRegistry is BaseRegistry, AccessControl { return super.ownerOf(tokenId); } - function register( - string calldata label, - address owner, - IRegistry registry, - uint96 flags - ) public onlyRole(REGISTRAR_ROLE) { - uint256 tokenId = uint256(keccak256(bytes(label))); - + function register(string calldata label, address owner, IRegistry registry, uint32 flags, uint64 expires) + public + onlyRole(REGISTRAR_ROLE) + returns (uint256 tokenId) + { + flags &= FLAGS_MASK; + tokenId = (uint256(keccak256(bytes(label))) & ~uint256(FLAGS_MASK)) | flags; + (, uint96 oldFlags) = datastore.getSubregistry(tokenId); - uint64 expires = uint64(oldFlags); - if (expires >= block.timestamp) { + uint64 oldExpiry = uint64(oldFlags >> 32); + if (oldExpiry >= block.timestamp) { revert NameAlreadyRegistered(label); } - - _mint(label, owner, registry, flags); + + _mint(tokenId, owner, registry, uint96(flags) | (uint96(expires) << 32)); + emit NewSubname(label); + return tokenId; } - function renew( - string calldata label, - uint64 expires - ) public onlyRole(REGISTRAR_ROLE) { - uint256 tokenId = uint256(keccak256(bytes(label))); + function renew(uint256 tokenId, uint64 expires) public onlyRole(REGISTRAR_ROLE) { (address subregistry, uint96 flags) = datastore.getSubregistry(tokenId); - uint64 oldExpiration = uint64(flags); + uint64 oldExpiration = uint64(flags >> 32); if (oldExpiration < block.timestamp) { - revert NameExpired(label); + revert NameExpired(tokenId); } if (expires < oldExpiration) { revert CannotReduceExpiration(oldExpiration, expires); } - datastore.setSubregistry(tokenId, subregistry, (flags & SUBREGISTRY_FLAGS_MASK) | uint96(expires)); + datastore.setSubregistry(tokenId, subregistry, (flags & FLAGS_MASK) | (uint96(expires) << 32)); } - function locked( - string memory label - ) external view returns (bool) { - uint256 tokenId = uint256(keccak256(bytes(label))); - (, uint96 flags) = datastore.getSubregistry(tokenId); - return flags & SUBREGISTRY_FLAG_LOCKED != 0; + function nameData(uint256 tokenId) external view returns (uint64 expiry, uint32 flags) { + (, uint96 _flags) = datastore.getSubregistry(tokenId); + return (uint64(_flags >> 32), uint32(_flags)); } - function lock(string calldata label) external onlyTokenOwner(label) { - uint256 tokenId = uint256(keccak256(bytes(label))); - (address subregistry, uint96 flags) = datastore.getSubregistry(tokenId); - datastore.setSubregistry(tokenId, subregistry, flags & SUBREGISTRY_FLAG_LOCKED); + function lock(uint256 tokenId, uint32 flags) + external + onlyTokenOwner(tokenId) + withSubregistryFlags(tokenId, FLAG_FLAGS_LOCKED, 0) + returns (uint256 newTokenId) + { + (address subregistry, uint96 oldFlags) = datastore.getSubregistry(tokenId); + uint96 newFlags = oldFlags | (flags & FLAGS_MASK); + if (newFlags != oldFlags) { + address owner = ownerOf(tokenId); + _burn(owner, tokenId, 1); + newTokenId = (tokenId & ~uint256(FLAGS_MASK)) | (newFlags & FLAGS_MASK); + _mint(newTokenId, owner, IRegistry(subregistry), newFlags); + } else { + newTokenId = tokenId; + } } - function setSubregistry( - string calldata label, - IRegistry registry - ) + function setSubregistry(uint256 tokenId, IRegistry registry) external - onlyTokenOwner(label) - withSubregistryFlags(label, SUBREGISTRY_FLAG_LOCKED, 0) + onlyTokenOwner(tokenId) + withSubregistryFlags(tokenId, FLAG_SUBREGISTRY_LOCKED, 0) { - uint256 tokenId = uint256(keccak256(bytes(label))); (, uint96 flags) = datastore.getSubregistry(tokenId); datastore.setSubregistry(tokenId, address(registry), flags); } - function supportsInterface( - bytes4 interfaceId - ) public view override(BaseRegistry, AccessControl) returns (bool) { - return - interfaceId == type(IRegistry).interfaceId || - super.supportsInterface(interfaceId); + function setResolver(uint256 tokenId, address resolver) + external + onlyTokenOwner(tokenId) + withSubregistryFlags(tokenId, FLAG_RESOLVER_LOCKED, 0) + { + (, uint96 flags) = datastore.getResolver(tokenId); + datastore.setResolver(tokenId, resolver, flags); + } + + function supportsInterface(bytes4 interfaceId) public view override(BaseRegistry, AccessControl) returns (bool) { + return interfaceId == type(IRegistry).interfaceId || super.supportsInterface(interfaceId); } - function getSubregistry(string calldata label) external override virtual view returns (IRegistry) { + function getSubregistry(string calldata label) external view virtual override returns (IRegistry) { (address subregistry, uint96 flags) = datastore.getSubregistry(uint256(keccak256(bytes(label)))); uint64 expires = uint64(flags); if (expires >= block.timestamp) { @@ -116,7 +128,7 @@ contract ETHRegistry is BaseRegistry, AccessControl { return IRegistry(subregistry); } - function getResolver(string calldata label) external override virtual view returns (address) { + function getResolver(string calldata label) external view virtual override returns (address) { (address resolver, uint96 flags) = datastore.getResolver(uint256(keccak256(bytes(label)))); uint64 expires = uint64(flags); if (expires >= block.timestamp) { diff --git a/contracts/src/registry/IERC1155Singleton.sol b/contracts/src/registry/IERC1155Singleton.sol index 39cb03b..b0dc35a 100644 --- a/contracts/src/registry/IERC1155Singleton.sol +++ b/contracts/src/registry/IERC1155Singleton.sol @@ -5,4 +5,4 @@ import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; interface IERC1155Singleton is IERC1155 { function ownerOf(uint256 id) external view returns (address owner); -} \ No newline at end of file +} diff --git a/contracts/src/registry/IRegistry.sol b/contracts/src/registry/IRegistry.sol index bd7a242..a62e0b2 100644 --- a/contracts/src/registry/IRegistry.sol +++ b/contracts/src/registry/IRegistry.sol @@ -5,7 +5,7 @@ import {IERC1155Singleton} from "./IERC1155Singleton.sol"; interface IRegistry is IERC1155Singleton { event NewSubname(string label); - + /** * @dev Fetches the registry for a subdomain of the current registry. * @param label The label to resolve. diff --git a/contracts/src/registry/IRegistryDatastore.sol b/contracts/src/registry/IRegistryDatastore.sol index 54a7a0e..06b38b4 100644 --- a/contracts/src/registry/IRegistryDatastore.sol +++ b/contracts/src/registry/IRegistryDatastore.sol @@ -1,14 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.13; +/** + * @dev Interface for the ENSv2 registry datastore, which stores subregistry and resolver addresses and flags + * for all names, keyed by registry address and `keccak256(label)`. + * The lower 32 bits of label hashes are masked out for storage and retreival, allowing these bits to be used + * by registry implementations for different versions of tokens that reference the same underlying name. This + * means that two labelHashes that differ only in the least-significant 32 bits will resolve to the same name. + */ interface IRegistryDatastore { - event SubregistryUpdate(address indexed registry, uint256 indexed tokenId, address subregistry, uint96 flags); - event ResolverUpdate(address indexed registry, uint256 indexed tokenId, address resolver, uint96 flags); + event SubregistryUpdate(address indexed registry, uint256 indexed labelHash, address subregistry, uint96 flags); + event ResolverUpdate(address indexed registry, uint256 indexed labelHash, address resolver, uint96 flags); - function getSubregistry(address registry, uint256 tokenId) external view returns(address subregistry, uint96 flags); - function getSubregistry(uint256 tokenId) external view returns(address subregistry, uint96 flags); - function getResolver(address registry, uint256 tokenId) external view returns(address resolver, uint96 flags); - function getResolver(uint256 tokenId) external view returns(address resolver, uint96 flags); - function setSubregistry(uint256 tokenId, address subregistry, uint96 flags) external; - function setResolver(uint256 tokenId, address resolver, uint96 flags) external; + function getSubregistry(address registry, uint256 labelHash) + external + view + returns (address subregistry, uint96 flags); + function getSubregistry(uint256 labelHash) external view returns (address subregistry, uint96 flags); + function getResolver(address registry, uint256 labelHash) external view returns (address resolver, uint96 flags); + function getResolver(uint256 labelHash) external view returns (address resolver, uint96 flags); + function setSubregistry(uint256 labelHash, address subregistry, uint96 flags) external; + function setResolver(uint256 labelHash, address resolver, uint96 flags) external; } diff --git a/contracts/src/registry/RegistryDatastore.sol b/contracts/src/registry/RegistryDatastore.sol index 3f17e64..b042ae8 100644 --- a/contracts/src/registry/RegistryDatastore.sol +++ b/contracts/src/registry/RegistryDatastore.sol @@ -1,39 +1,44 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.13; -import { IRegistryDatastore } from './IRegistryDatastore.sol'; +import {IRegistryDatastore} from "./IRegistryDatastore.sol"; contract RegistryDatastore is IRegistryDatastore { - mapping(address registry => mapping(uint256 tokenId => uint256)) internal subregistries; - mapping(address registry => mapping(uint256 tokenId => uint256)) internal resolvers; - - function getSubregistry(address registry, uint256 tokenId) public view returns(address subregistry, uint96 flags) { - uint256 data = subregistries[registry][tokenId]; + uint256 LABEL_HASH_MASK = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000; + mapping(address registry => mapping(uint256 labelHash => uint256)) internal subregistries; + mapping(address registry => mapping(uint256 labelHash => uint256)) internal resolvers; + + function getSubregistry(address registry, uint256 labelHash) + public + view + returns (address subregistry, uint96 flags) + { + uint256 data = subregistries[registry][labelHash & LABEL_HASH_MASK]; subregistry = address(uint160(data)); flags = uint96(data >> 160); } - function getSubregistry(uint256 tokenId) external view returns(address subregistry, uint96 flags) { - return getSubregistry(msg.sender, tokenId); + function getSubregistry(uint256 labelHash) external view returns (address subregistry, uint96 flags) { + return getSubregistry(msg.sender, labelHash); } - function getResolver(address registry, uint256 tokenId) public view returns(address resolver, uint96 flags) { - uint256 data = subregistries[registry][tokenId]; + function getResolver(address registry, uint256 labelHash) public view returns (address resolver, uint96 flags) { + uint256 data = resolvers[registry][labelHash & LABEL_HASH_MASK]; resolver = address(uint160(data)); flags = uint96(data >> 160); } - function getResolver(uint256 tokenId) external view returns(address resolver, uint96 flags) { - return getResolver(msg.sender, tokenId); + function getResolver(uint256 labelHash) external view returns (address resolver, uint96 flags) { + return getResolver(msg.sender, labelHash); } - function setSubregistry(uint256 tokenId, address subregistry, uint96 flags) external { - subregistries[msg.sender][tokenId] = (uint256(flags) << 160) | uint256(uint160(subregistry)); - emit SubregistryUpdate(msg.sender, tokenId, subregistry, flags); + function setSubregistry(uint256 labelHash, address subregistry, uint96 flags) external { + subregistries[msg.sender][labelHash & LABEL_HASH_MASK] = (uint256(flags) << 160) | uint256(uint160(subregistry)); + emit SubregistryUpdate(msg.sender, labelHash & LABEL_HASH_MASK, subregistry, flags); } - function setResolver(uint256 tokenId, address resolver, uint96 flags) external { - resolvers[msg.sender][tokenId] = (uint256(flags) << 160) | uint256(uint160(resolver)); - emit ResolverUpdate(msg.sender, tokenId, resolver, flags); + function setResolver(uint256 labelHash, address resolver, uint96 flags) external { + resolvers[msg.sender][labelHash & LABEL_HASH_MASK] = (uint256(flags) << 160) | uint256(uint160(resolver)); + emit ResolverUpdate(msg.sender, labelHash & LABEL_HASH_MASK, resolver, flags); } } diff --git a/contracts/src/registry/RootRegistry.sol b/contracts/src/registry/RootRegistry.sol index 5b0d89b..6f03995 100644 --- a/contracts/src/registry/RootRegistry.sol +++ b/contracts/src/registry/RootRegistry.sol @@ -18,7 +18,7 @@ contract RootRegistry is BaseRegistry, AccessControl { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } - function uri(uint256 /*id*/) public override pure returns (string memory) { + function uri(uint256 /*id*/ ) public pure override returns (string memory) { return ""; } @@ -26,45 +26,36 @@ contract RootRegistry is BaseRegistry, AccessControl { external onlyRole(SUBDOMAIN_ISSUER_ROLE) { - _mint(label, owner, registry, locked ? SUBREGISTRY_FLAG_LOCKED : 0); + uint256 tokenId = uint256(keccak256(bytes(label))); + _mint(tokenId, owner, registry, locked ? SUBREGISTRY_FLAG_LOCKED : 0); + emit NewSubname(label); } - function burn(string calldata label) + function burn(uint256 tokenId) external onlyRole(SUBDOMAIN_ISSUER_ROLE) - withSubregistryFlags(label, SUBREGISTRY_FLAGS_MASK, 0) + withSubregistryFlags(tokenId, SUBREGISTRY_FLAGS_MASK, 0) { - uint256 tokenId = uint256(keccak256(bytes(label))); address owner = ownerOf(tokenId); _burn(owner, tokenId, 1); datastore.setSubregistry(tokenId, address(0), 0); } - function lock(string calldata label) - external - onlyRole(SUBDOMAIN_ISSUER_ROLE) - { - uint256 tokenId = uint256(keccak256(bytes(label))); + function lock(uint256 tokenId) external onlyRole(SUBDOMAIN_ISSUER_ROLE) { (address subregistry, uint96 flags) = datastore.getSubregistry(tokenId); datastore.setSubregistry(tokenId, subregistry, flags & SUBREGISTRY_FLAG_LOCKED); } - function setSubregistry(string calldata label, IRegistry registry) + function setSubregistry(uint256 tokenId, IRegistry registry) external - onlyTokenOwner(label) - withSubregistryFlags(label, SUBREGISTRY_FLAGS_MASK, 0) + onlyTokenOwner(tokenId) + withSubregistryFlags(tokenId, SUBREGISTRY_FLAGS_MASK, 0) { - uint256 tokenId = uint256(keccak256(bytes(label))); (, uint96 flags) = datastore.getSubregistry(tokenId); datastore.setSubregistry(tokenId, address(registry), flags); } - function supportsInterface(bytes4 interfaceId) - public - view - override(BaseRegistry, AccessControl) - returns (bool) - { + function supportsInterface(bytes4 interfaceId) public view override(BaseRegistry, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } } diff --git a/contracts/src/registry/UserRegistry.sol b/contracts/src/registry/UserRegistry.sol index f0c691b..0922f9a 100644 --- a/contracts/src/registry/UserRegistry.sol +++ b/contracts/src/registry/UserRegistry.sol @@ -17,11 +17,7 @@ contract UserRegistry is BaseRegistry { IRegistry public parent; string public label; - constructor( - IRegistry _parent, - string memory _label, - IRegistryDatastore _datastore - ) BaseRegistry(_datastore) { + constructor(IRegistry _parent, string memory _label, IRegistryDatastore _datastore) BaseRegistry(_datastore) { parent = _parent; label = _label; } @@ -29,61 +25,47 @@ contract UserRegistry is BaseRegistry { modifier onlyNameOwner() { address owner = parent.ownerOf(uint256(keccak256(bytes(label)))); if (owner != msg.sender) { - revert AccessDenied("", owner, msg.sender); + revert AccessDenied(0, owner, msg.sender); } _; } - function uri(uint256 /*id*/) public override pure returns (string memory) { + function uri(uint256 /*id*/ ) public pure override returns (string memory) { return ""; } - function mint( - string calldata _label, - address owner, - IRegistry registry, - uint96 flags - ) external onlyNameOwner { - _mint(_label, owner, registry, flags); + function mint(string calldata _label, address owner, IRegistry registry, uint96 flags) external onlyNameOwner { + uint256 tokenId = uint256(keccak256(bytes(_label))); + _mint(tokenId, owner, registry, flags); + emit NewSubname(label); } - function burn( - string calldata _label - ) external onlyNameOwner withSubregistryFlags(_label, SUBREGISTRY_FLAG_LOCKED, 0) { - uint256 tokenId = uint256(keccak256(bytes(_label))); + function burn(uint256 tokenId) external onlyNameOwner withSubregistryFlags(tokenId, SUBREGISTRY_FLAG_LOCKED, 0) { address owner = ownerOf(tokenId); _burn(owner, tokenId, 1); datastore.setSubregistry(tokenId, address(0), 0); } - function locked( - string memory _label - ) external view returns (bool) { - uint256 tokenId = uint256(keccak256(bytes(_label))); + function locked(uint256 tokenId) external view returns (bool) { (, uint96 flags) = datastore.getSubregistry(tokenId); return flags & SUBREGISTRY_FLAG_LOCKED != 0; } - function lock(string calldata _label) external onlyTokenOwner(_label) { - uint256 tokenId = uint256(keccak256(bytes(_label))); + function lock(uint256 tokenId) external onlyTokenOwner(tokenId) { (address subregistry, uint96 flags) = datastore.getSubregistry(tokenId); datastore.setSubregistry(tokenId, subregistry, flags & SUBREGISTRY_FLAG_LOCKED); } - function setSubregistry( - string calldata _label, - IRegistry registry - ) external onlyTokenOwner(_label) withSubregistryFlags(_label, SUBREGISTRY_FLAG_LOCKED, 0) { - uint256 tokenId = uint256(keccak256(bytes(_label))); + function setSubregistry(uint256 tokenId, IRegistry registry) + external + onlyTokenOwner(tokenId) + withSubregistryFlags(tokenId, SUBREGISTRY_FLAG_LOCKED, 0) + { (, uint96 flags) = datastore.getSubregistry(tokenId); datastore.setSubregistry(tokenId, address(registry), flags); } - function supportsInterface( - bytes4 interfaceId - ) public view override returns (bool) { - return - interfaceId == type(IRegistry).interfaceId || - super.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IRegistry).interfaceId || super.supportsInterface(interfaceId); } } diff --git a/contracts/src/utils/UniversalResolver.sol b/contracts/src/utils/UniversalResolver.sol index a7a83b3..9bb718f 100644 --- a/contracts/src/utils/UniversalResolver.sol +++ b/contracts/src/utils/UniversalResolver.sol @@ -18,9 +18,7 @@ contract UniversalResolver { * @return reg A registry responsible for the name. * @return exact A boolean that is true if the registry is an exact match for `name`. */ - function getRegistry( - bytes calldata name - ) public view returns (IRegistry reg, bool exact) { + function getRegistry(bytes calldata name) public view returns (IRegistry reg, bool exact) { uint8 len = uint8(name[0]); if (len == 0) { return (root, true); @@ -43,7 +41,7 @@ contract UniversalResolver { * @return The resolver responsible for this name, or `address(0)` if none. */ function getResolver(bytes calldata name) public view returns (address) { - (IRegistry reg, ) = getRegistry(name); + (IRegistry reg,) = getRegistry(name); uint8 len = uint8(name[0]); string memory label = string(name[1:len + 1]); return reg.getResolver(label); diff --git a/contracts/tasks/esm_fix.cts b/contracts/tasks/esm_fix.cts new file mode 100644 index 0000000..13e532f --- /dev/null +++ b/contracts/tasks/esm_fix.cts @@ -0,0 +1,21 @@ +import fs = require("fs/promises"); +import task_names = require("hardhat/builtin-tasks/task-names"); +import config = require("hardhat/config"); +import path = require("path"); + +config + .subtask(task_names.TASK_COMPILE_SOLIDITY) + .setAction(async (_, { config }, runSuper) => { + const superRes = await runSuper(); + + try { + await fs.writeFile( + path.join(config.paths.artifacts, "package.json"), + '{ "type": "commonjs" }' + ); + } catch (error) { + console.error("Error writing package.json: ", error); + } + + return superRes; + }); diff --git a/contracts/test/ETHRegistry.t.sol b/contracts/test/ETHRegistry.t.sol new file mode 100644 index 0000000..b8ec19d --- /dev/null +++ b/contracts/test/ETHRegistry.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; + +import "src/registry/ETHRegistry.sol"; +import "src/registry/RegistryDatastore.sol"; + +contract TestETHRegistry is Test, ERC1155Holder { + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + RegistryDatastore datastore; + ETHRegistry registry; + + function setUp() public { + datastore = new RegistryDatastore(); + registry = new ETHRegistry(datastore); + registry.grantRole(registry.REGISTRAR_ROLE(), address(this)); + } + + function test_register_unlocked() public { + uint256 expectedId = + uint256(keccak256("test2") & 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8); + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(0), address(this), expectedId, 1); + + uint256 tokenId = registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400); + vm.assertEq(tokenId, expectedId); + } + + function test_register_locked() public { + uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED(); + uint256 expectedId = + uint256(keccak256("test2") & 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8) | flags; + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(0), address(this), expectedId, 1); + + uint256 tokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400); + vm.assertEq(tokenId, expectedId); + } + + function test_lock_name() public { + uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED(); + uint256 oldTokenId = registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(this), address(0), oldTokenId, 1); + uint256 expectedTokenId = oldTokenId | flags; + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(0), address(this), expectedTokenId, 1); + + uint256 newTokenId = registry.lock(oldTokenId, flags); + vm.assertEq(newTokenId, expectedTokenId); + } + + function test_cannot_unlock_name() public { + uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED(); + + uint256 oldTokenId = registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400); + uint256 newTokenId = registry.lock(oldTokenId, 0); + vm.assertEq(oldTokenId, newTokenId); + } + + function testFail_cannot_mint_duplicates() public { + uint32 flags = registry.FLAG_SUBREGISTRY_LOCKED() | registry.FLAG_RESOLVER_LOCKED(); + + registry.register("test2", address(this), registry, flags, uint64(block.timestamp) + 86400); + registry.register("test2", address(this), registry, 0, uint64(block.timestamp) + 86400); + } +} diff --git a/contracts/test/Ens.t.ts b/contracts/test/Ens.t.ts index a64f13c..a57916d 100644 --- a/contracts/test/Ens.t.ts +++ b/contracts/test/Ens.t.ts @@ -1,8 +1,7 @@ -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; +import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers.js"; import { expect } from "chai"; -import { getAddress } from "viem"; -import { deployEnsFixture, registerName } from "./fixtures/deployEnsFixture"; -import { dnsEncodeName } from "./utils/utils"; +import { deployEnsFixture, registerName } from "./fixtures/deployEnsFixture.js"; +import { dnsEncodeName } from "./utils/utils.js"; describe("Ens", function () { it("returns eth registry for eth", async () => { @@ -11,8 +10,8 @@ describe("Ens", function () { ); const [fetchedEthRegistry, isExact] = await universalResolver.read.getRegistry([dnsEncodeName("eth")]); - expect(isExact).to.be.true; - expect(fetchedEthRegistry).to.equal(getAddress(ethRegistry.address)); + expect(isExact).toBe(true); + expect(fetchedEthRegistry).toEqualAddress(ethRegistry.address); }); it("returns eth registry for test.eth without user registry", async () => { @@ -23,7 +22,7 @@ describe("Ens", function () { const [registry, isExact] = await universalResolver.read.getRegistry([ dnsEncodeName("test.eth"), ]); - expect(isExact).to.be.false; - expect(registry).to.equal(getAddress(ethRegistry.address)); + expect(isExact).toBe(false); + expect(registry).toEqualAddress(ethRegistry.address); }); }); diff --git a/contracts/test/RegistryDatastore.t.sol b/contracts/test/RegistryDatastore.t.sol new file mode 100644 index 0000000..c9d510b --- /dev/null +++ b/contracts/test/RegistryDatastore.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "src/registry/RegistryDatastore.sol"; + +contract TestETHRegistry is Test { + uint256 LABEL_HASH_MASK = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000; + uint256 labelHash = uint256(keccak256("test")); + RegistryDatastore datastore; + + function setUp() public { + datastore = new RegistryDatastore(); + } + + function test_GetSetSubregistry_MsgSender() public { + // set subregistry with empty flags + datastore.setSubregistry(labelHash, address(this), 0); + + address subregistry; + uint96 flags; + + (subregistry, flags) = datastore.getSubregistry(labelHash); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0); + + (subregistry, flags) = datastore.getSubregistry(address(this), labelHash); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0); + } + + function test_GetSetSubregistry_OtherRegistry() public { + DummyRegistry r = new DummyRegistry(datastore); + r.setSubregistry(labelHash, address(this), 0); + + address subregistry; + uint96 flags; + + (subregistry, flags) = datastore.getSubregistry(labelHash); + vm.assertEq(subregistry, address(0)); + vm.assertEq(flags, 0); + + (subregistry, flags) = datastore.getSubregistry(address(r), labelHash); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0); + } + + function test_GetSetSubregistry_32BitCustomFlags() public { + datastore.setSubregistry(labelHash, address(this), 0x80000001); + + address subregistry; + uint96 flags; + + (subregistry, flags) = datastore.getSubregistry(labelHash); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0x80000001); + + // get with flags on labelhash + (subregistry, flags) = datastore.getSubregistry((labelHash & LABEL_HASH_MASK) | 0x80000001); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0x80000001); + } + + function test_GetSetSubregistry_96BitCustomFlags() public { + datastore.setSubregistry(labelHash, address(this), 0x800000000000000000000001); + + (address subregistry, uint96 flags) = datastore.getSubregistry(labelHash); + vm.assertEq(subregistry, address(this)); + vm.assertEq(flags, 0x800000000000000000000001); + } + + function test_GetSetResolver_MsgSender() public { + datastore.setResolver(labelHash, address(this), 0); + + address resolver; + uint96 flags; + + (resolver, flags) = datastore.getResolver(labelHash); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0); + + (resolver, flags) = datastore.getResolver(address(this), labelHash); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0); + } + + function test_GetSetResolver_OtherRegistry() public { + DummyRegistry r = new DummyRegistry(datastore); + r.setResolver(labelHash, address(this), 0); + + address resolver; + uint96 flags; + + (resolver, flags) = datastore.getResolver(labelHash); + vm.assertEq(resolver, address(0)); + vm.assertEq(flags, 0); + + (resolver, flags) = datastore.getResolver(address(r), labelHash); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0); + } + + function test_GetSetResolver_32BitCustomFlags() public { + datastore.setResolver(labelHash, address(this), 0x80000001); + + address resolver; + uint96 flags; + + (resolver, flags) = datastore.getResolver(labelHash); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0x80000001); + + // get with flags on labelhash + (resolver, flags) = datastore.getResolver((labelHash & LABEL_HASH_MASK) | 0x80000001); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0x80000001); + } + + function test_GetSetResolver_96BitCustomFlags() public { + datastore.setResolver(labelHash, address(this), 0x800000000000000000000001); + + (address resolver, uint96 flags) = datastore.getResolver(labelHash); + vm.assertEq(resolver, address(this)); + vm.assertEq(flags, 0x800000000000000000000001); + } +} + +contract DummyRegistry { + RegistryDatastore datastore; + + constructor(RegistryDatastore _datastore) { + datastore = _datastore; + } + + function setSubregistry(uint256 labelHash, address subregistry, uint96 flags) public { + datastore.setSubregistry(labelHash, subregistry, flags); + } + + function setResolver(uint256 labelHash, address resolver, uint96 flags) public { + datastore.setResolver(labelHash, resolver, flags); + } +} diff --git a/contracts/test/fixtures/deployEnsFixture.ts b/contracts/test/fixtures/deployEnsFixture.ts index 368a7d8..4061b63 100644 --- a/contracts/test/fixtures/deployEnsFixture.ts +++ b/contracts/test/fixtures/deployEnsFixture.ts @@ -1,31 +1,46 @@ import hre from "hardhat"; -import { Address, bytesToHex, keccak256, stringToHex, zeroAddress } from "viem"; -import { packetToBytes } from "../utils/utils"; +import { Address, bytesToHex, keccak256, stringToHex } from "viem"; +import { packetToBytes } from "../utils/utils.js"; export async function deployEnsFixture() { - const walletClients = await hre.viem.getWalletClients(); + const publicClient = await hre.viem.getPublicClient(); + const accounts = await hre.viem + .getWalletClients() + .then((clients) => clients.map((c) => c.account)); + const datastore = await hre.viem.deployContract("RegistryDatastore", []); - const rootRegistry = await hre.viem.deployContract("RootRegistry", [datastore.address]); - const ethRegistry = await hre.viem.deployContract("ETHRegistry", [datastore.address]); + const rootRegistry = await hre.viem.deployContract("RootRegistry", [ + datastore.address, + ]); + const ethRegistry = await hre.viem.deployContract("ETHRegistry", [ + datastore.address, + ]); const universalResolver = await hre.viem.deployContract("UniversalResolver", [ rootRegistry.address, ]); await rootRegistry.write.grantRole([ keccak256(stringToHex("SUBDOMAIN_ISSUER_ROLE")), - walletClients[0].account.address, + accounts[0].address, ]); await ethRegistry.write.grantRole([ keccak256(stringToHex("REGISTRAR_ROLE")), - walletClients[0].account.address, + accounts[0].address, ]); await rootRegistry.write.mint([ "eth", - walletClients[0].account.address, + accounts[0].address, ethRegistry.address, true, ]); - return { datastore, rootRegistry, ethRegistry, universalResolver }; + return { + publicClient, + accounts, + datastore, + rootRegistry, + ethRegistry, + universalResolver, + }; } export type EnsFixture = Awaited>; @@ -57,16 +72,18 @@ export const registerName = async ({ expiry = BigInt(Math.floor(Date.now() / 1000) + 1000000), owner: owner_, subregistry = "0x0000000000000000000000000000000000000000", - locked = false, + subregistryLocked = false, + resolverLocked = false, }: Pick & { label: string; expiry?: bigint; owner?: Address; subregistry?: Address; - locked?: boolean; + subregistryLocked?: boolean; + resolverLocked?: boolean; }) => { const owner = owner_ ?? (await hre.viem.getWalletClients())[0].account.address; - const flags = (locked ? BigInt(0x100000000) : BigInt(0)) | expiry; - await ethRegistry.write.register([label, owner, subregistry, flags]); + const flags = (subregistryLocked ? 1 : 0) | (resolverLocked ? 2 : 0); + return ethRegistry.write.register([label, owner, subregistry, flags, expiry]); }; diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index 3b5f8f0..b983f7d 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -1,27 +1,26 @@ { "compilerOptions": { - // Enable latest features - "lib": ["ESNext"], - "target": "ES2020", - "module": "NodeNext", + "target": "ES2022", + "module": "Node16", "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Bundler mode - "moduleResolution": "NodeNext", - "allowImportingTsExtensions": true, - "noEmit": true, - "esModuleInterop": true, - - // Best practices "strict": true, + "esModuleInterop": true, + "moduleResolution": "NodeNext", + "outDir": "build", "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": [ + "./tasks/**/*.ts", + "./tasks/**/*.cts", + "./test/**/*.ts", + "./artifacts/**/*.d.ts" + ], + "ts-node": { + "experimentalResolver": true, + "experimentalSpecifierResolution": "node", + "files": true + }, + "files": ["hardhat.config.cts"] } diff --git a/package.json b/package.json index aa6c881..1ea12dd 100644 --- a/package.json +++ b/package.json @@ -6,5 +6,8 @@ ], "devDependencies": { "typescript": "^5.4.5" + }, + "patchedDependencies": { + "@nomicfoundation/hardhat-viem@2.0.1": "patches/@nomicfoundation%2Fhardhat-viem@2.0.1.patch" } } diff --git a/patches/@nomicfoundation%2Fhardhat-viem@2.0.1.patch b/patches/@nomicfoundation%2Fhardhat-viem@2.0.1.patch new file mode 100644 index 0000000..3f0612f --- /dev/null +++ b/patches/@nomicfoundation%2Fhardhat-viem@2.0.1.patch @@ -0,0 +1,118 @@ +diff --git a/internal/clients.js b/node_modules/internal/clients.js +index 9466df2..e512716 100644 +--- a/internal/clients.js ++++ b/internal/clients.js +@@ -47,7 +47,8 @@ async function innerGetPublicClient(provider, chain, publicClientConfig) { + const parameters = { ...defaultParameters, ...publicClientConfig }; + const publicClient = viem.createPublicClient({ + chain, +- transport: viem.custom(provider), ++ transport: viem.custom(provider, { retryCount: 0 }), ++ ccipRead: false, + ...parameters, + }); + return publicClient; +@@ -80,7 +81,7 @@ async function innerGetWalletClients(provider, chain, accounts, walletClientConf + const walletClients = accounts.map((account) => viem.createWalletClient({ + chain, + account, +- transport: viem.custom(provider), ++ transport: viem.custom(provider, { retryCount: 0 }), + ...parameters, + })); + return walletClients; +@@ -123,7 +124,7 @@ async function innerGetTestClient(provider, chain, mode, testClientConfig) { + const testClient = viem.createTestClient({ + mode, + chain, +- transport: viem.custom(provider), ++ transport: viem.custom(provider, { retryCount: 0 }), + ...parameters, + }); + return testClient; +diff --git a/internal/tasks.js b/internal/tasks.js +index 411ea51..b2cb6ab 100644 +--- a/internal/tasks.js ++++ b/internal/tasks.js +@@ -105,6 +105,12 @@ declare module "hardhat/types/artifacts" { + .map((name) => `${name}: never;`) + .join("\n ")} + } ++ ++ interface ContractTypesMap { ++ ${Array.from(duplicateContractNames) ++ .map((name) => `${name}: never;`) ++ .join("\n ")} ++ } + } + `; + } +@@ -173,6 +179,7 @@ function generateArtifactsDefinition(contractTypeData) { + return `${AUTOGENERATED_FILE_PREFACE} + + import "hardhat/types/artifacts"; ++import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; + + ${contractTypeData + .map((ctd) => `import { ${ctd.typeName} } from "./${ctd.contractName}";`) +@@ -187,6 +194,15 @@ declare module "hardhat/types/artifacts" { + .map((ctd) => `["${ctd.fqn}"]: ${ctd.typeName};`) + .join("\n ")} + } ++ ++ interface ContractTypesMap { ++ ${contractTypeData ++ .map((ctd) => `["${ctd.contractName}"]: GetContractReturnType<${ctd.typeName}["abi"]>;`) ++ .join("\n ")} ++ ${contractTypeData ++ .map((ctd) => `["${ctd.fqn}"]: GetContractReturnType<${ctd.typeName}["abi"]>;`) ++ .join("\n ")} ++ } + } + `; + } +diff --git a/internal/type-extensions.d.ts b/internal/type-extensions.d.ts +index 59bcafe..522bfd0 100644 +--- a/internal/type-extensions.d.ts ++++ b/internal/type-extensions.d.ts +@@ -1,18 +1,10 @@ + import type { Address, PublicClientConfig, WalletClientConfig, TestClientConfig } from "viem"; +-import type { PublicClient, TestClient, WalletClient, deployContract, sendDeploymentTransaction, getContractAt } from "../types"; ++import type { HardhatViemHelpers } from "../types"; + import "hardhat/types/runtime"; + import "hardhat/types/artifacts"; + declare module "hardhat/types/runtime" { + interface HardhatRuntimeEnvironment { +- viem: { +- getPublicClient(publicClientConfig?: Partial): Promise; +- getWalletClients(walletClientConfig?: Partial): Promise; +- getWalletClient(address: Address, walletClientConfig?: Partial): Promise; +- getTestClient(testClientConfig?: Partial): Promise; +- deployContract: typeof deployContract; +- sendDeploymentTransaction: typeof sendDeploymentTransaction; +- getContractAt: typeof getContractAt; +- }; ++ viem: HardhatViemHelpers; + } + } + declare module "hardhat/types/artifacts" { +diff --git a/types.d.ts b/types.d.ts +index 0ec3f21..e95ebbb 100644 +--- a/types.d.ts ++++ b/types.d.ts +@@ -35,5 +35,14 @@ export declare function sendDeploymentTransaction(contractNam + deploymentTransaction: GetTransactionReturnType; + }>; + export declare function getContractAt(contractName: ContractName, address: viemT.Address, config?: GetContractAtConfig): Promise; ++export interface HardhatViemHelpers { ++ getPublicClient(publicClientConfig?: Partial): Promise; ++ getWalletClients(walletClientConfig?: Partial): Promise; ++ getWalletClient(address: viemT.Address, walletClientConfig?: Partial): Promise; ++ getTestClient(testClientConfig?: Partial): Promise; ++ deployContract: typeof deployContract; ++ sendDeploymentTransaction: typeof sendDeploymentTransaction; ++ getContractAt: typeof getContractAt; ++} + export type { AbiParameterToPrimitiveType } from "abitype"; + //# sourceMappingURL=types.d.ts.map +\ No newline at end of file