Skip to content

Refactor PermissionedResolver (Idea 2)#255

Draft
adraffy wants to merge 11 commits intomainfrom
feat/permres-fs
Draft

Refactor PermissionedResolver (Idea 2)#255
adraffy wants to merge 11 commits intomainfrom
feat/permres-fs

Conversation

@adraffy
Copy link
Member

@adraffy adraffy commented Mar 20, 2026

  • updated DNSTXTResolver
    • added IDataResolver (ENSIP-24) support
  • added IRecordResolver
    • new ENSv2 events
  • added IResolverSetters
    • new ENSv2 setters
  • refactored IPermissionedResolver
    • removed aliasing
    • removed IExtendedResolver
    • added IDataResolver (ENSIP-24) support
    • added link(name, node), and getRecordId(node)
    • replaced grantNamedRoles() with grantRecordRoles(name, roles, account)
    • replaced grantNamed{Text|Addr}Roles() with grantSetterRoles(setter, account)
      • fine-grained permissions apply to IAddressResolver, ITextResolver, IDataResolver, IABIResolver, and IInterfaceResolver

  • updated devnet and tests to account for changes
  • updated resolutions.ts
    • changed write to writeV1
    • added writeV2

// inodes created automatically
resolver.setAddress(dnsEncode("raffy.eth"), 60, abi.encodePacked(0x5105));

// link: chonk.eth == raffy.eth:
resolver.link(dnsEncode("raffy.eth"), namehash("chonk.eth"));

// edit: linked => modifies same record as raffy.eth
resolver.setAddress(dnsEncode("chonk.eth"), 60, abi.encodePacked(0xdead));

// clears the underlying record => raffy.eth and chonk.eth are cleared
resolver.clear(dnsEncode("chonk.eth"));

// unlink
resolver.link(dnsEncode("raffy.eth"), bytes32(0));

// default record
resolver.setAddress(dnsEncode(""), 60, abi.encodePacked(0xdead));

// find an inode
uint256 recordId = resolver.getRecordId(namehash("raffy.eth'));

Records are created automatically by setters and assigned internal ID numbers (starting at 1).
To create a new record, `ROLE_NEW_RECORD` is required on root.
`getRecordId(node)` reveals the internal record ID.

`link(name, node)` makes `name` use the record currently used by `node`.
To link an existing record, `ROLE_LINK_RECORD` is required on root.

`link(name, bytes32(0))` unlinks `name` from the record.
Once a record is no longer referenced, it becomes unreachable and is effectively deleted.

`clear(name)` reset the record and requires `ROLE_CLEAR_RECORD` on the name or root.

Names without a record fall back to the default record, which can be updated using the empty name (`0x00`).

Every record setter has the form: `f(name, ...)`

Every record setter has a corresponding role:
| Function           | Role                   |
| ------------------ | ---------------------- |
| `setABI()`         | `ROLE_SET_ABI`         |
| `setAddress()`     | `ROLE_SET_ADDRESS`     |
| `setContentHash()` | `ROLE_SET_CONTENTHASH` |
| `setData()`        | `ROLE_SET_DATA`        |
| `setInterface()`   | `ROLE_SET_INTERFACE`   |
| `setName()`        | `ROLE_SET_NAME`        |
| `setText()`        | `ROLE_SET_TEXT`        |

Record setters can be granted with `getRecordRoles()` and are annotated
using ABI-encoded calldata: `abi.encodeWithSelector(bytes4(0), name)`.

Fine-grained record setters have the form: `f(name, <arg>, ...)`
They can be granted with `grantSetterRoles()` and are annotated
using truncated ABI-encoded calldata:
* w/data: `abi.encodeCall(setAddr, (name, coinType, "..."))`
* w/o data: `abi.encodeCall(setAddr, (name coinType, ""))`
* truncated: `abi.encodeWithSelector(setAddr.selector, name, coinType)`
The `roleBitmap` can be derived from the setter selector.

The following setters are fine-grained:
* `setAddress(name, coinType, ...)`
* `setData(name, key, ...)`
* `setText(name, key, ...)`
* `setABI(name, contentType, ...)`
* `setInterface(name, interfaceId, ...)`

The argument is hashed accordingly:
| Argument      | Part                                            |
| ------------- | ----------------------------------------------- |
| `uint256 arg` | `PermissionedResolverLib.partHash(arg)`         |
| `string arg`  | `PermissionedResolverLib.partHash(arg)`         |
| `bytes4 arg`  | `PermissionedResolverLib.partHash(uint32(arg))` |

Record setters check (4) EAC resources:
                                                     Part Hash
            Resources      +-----------------------------+----------------------------------+
                           |           Any (*)           |           Specific (1)           |
            +--------------+-----------------------------+----------------------------------+
            |      Any (*) |       resource(0, 0)        |      resource(0, <partHash>)     |
 Record ID  |--------------+-----------------------------+----------------------------------+
            | Specific (1) |   resource(<recordId>, 0)   | resource(<recordId>, <partHash>) |
            +--------------+-----------------------------+----------------------------------+

eg. `setText(name, "key", ...)` with `recordId = getRecordId(namehash(name))`
     will check the following resources for `ROLE_SET_TEXT` permission:
1. `resource(recordId, partHash("akeybc"))` => `arg="key"` for that record
2. `resource(recordId, 0)` => ANY part of that record
3. `resource(0, partHash("key"))` => `arg="key"` for ANY record
4. `resource(0, 0)` => ANY part of ANY record

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant