base / base/basenames

RegistrarController Incompatible with Direct Registry Ownership

Open
#153 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Solidity
Stars
164
Forks
115
PR merge metrics
No merged PRs in 30d

Description

## Problem Statement

When a domain is registered directly via `Registry.setSubnodeRecord()` (user owns the baseNode at Registry level), the `RegistrarController.register()` function cannot be used because it requires `BaseRegistrar` contract to own the baseNode, as enforced by the `live()` modifier.

This creates a **developer experience issue**: Developers who choose direct Registry ownership for full control cannot use the convenient `RegistrarController.register()` method with its atomic batching and payment handling.

## Current Behavior

### Standard Flow (BaseRegistrar Owns BaseNode)
1. `BaseRegistrar` contract owns the baseNode (e.g., `scenius.basetest.eth`)
2. `RegistrarController` is authorized as a controller
3. Users can call `RegistrarController.register()` which:
- Handles payment automatically
- Batches record setting atomically
- Sets expiry automatically

### Direct Registration Flow (User Owns BaseNode)
1. User registers domain directly via `Registry.setSubnodeRecord()`
2. User owns the baseNode directly at Registry level
3. **Cannot use `RegistrarController.register()`** - reverts with `RegistrarNotLive` error
4. Must use manual approach:
- `Registry.setSubnodeRecord()` (free, but separate transaction)
- Individual resolver operations (`setAddr`, `setText`) in separate transactions
- No atomic batching at contract level

## Code Reference

The issue is in `RegistrarController.sol`:

```solidity
modifier live() {
if (registry.owner(baseNode) != address(this)) revert RegistrarNotLive();
_;
}
```

This modifier prevents `register()` from working when the baseNode is owned directly by a user instead of `BaseRegistrar`.

**Related Contracts:**
- `BaseRegistrar.sol` - `live()` modifier (line 151-154)
- `RegistrarController.sol` - `register()` function (line 438)
- `Registry.sol` - `setSubnodeOwner()` and `setSubnodeRecord()` (lines 113-124, 85-92)

## Trade-offs of Direct Registry Ownership

**Advantages:**
- ✅ **FREE** - No payment required (`setSubnodeRecord()` is nonpayable)
- ✅ **Full Control** - User maintains direct ownership at Registry level
- ✅ **No Expiry** - Subnames do NOT expire (permanent ownership)

**Disadvantages:**
- ❌ **No Tokenization** - Subnames are NOT ERC721 tokens (no NFT minting)
- ❌ **No Expiry Mechanism** - Subnames never expire (may be pro or con)
- ❌ **No Atomic Batching** - Cannot batch record setting in single contract call (must use multiple operations)

## Real-World Impact

### Our Use Case: Catalogue Platform

We (Catalogue Platform) registered `scenius.basetest.eth` directly to maintain full control. We need to:
- Create subnames programmatically (e.g., `eros001.scenius.basetest.eth`)
- Set multiple ENS records atomically (address + 10 text records)
- Execute via Safe multisig transactions

**Current Workaround:**
We've implemented a workaround that executes 2 Safe transactions:
1. **Transaction 1**: `Registry.setSubnodeRecord()` - Creates subname
2. **Transaction 2**: Batched resolver operations - `setAddr` + 10 `setText` calls

This works, but requires:
- Multiple Safe transactions (2 instead of 1)
- Manual gas estimation (we had to fix GS025 errors by removing `safeTxGas: '1'`)
- No atomic guarantee at contract level (Safe batches, but not contract-level atomicity)

**Verification:**
We've successfully registered and verified multiple subnames:
- `eros006.scenius.basetest.eth` ✅
- `eros007.scenius.basetest.eth` ✅
- All 10 text records set correctly ✅

## Proposed Solutions

### Option 1: Extend RegistrarController to Support Direct Owners
Modify `RegistrarController` to support both:
- BaseRegistrar-owned baseNodes (current behavior)
- Direct owner baseNodes (new capability)

**Implementation approach:**
- Check if caller is authorized (owner or operator) for the baseNode
- Bypass the `live()` check when caller is directly authorized
- Maintain backward compatibility with existing BaseRegistrar flow

**Pros:**
- Single controller for both use cases
- Maintains existing API
- Backward compatible

**Cons:**
- Requires contract modification
- More complex authorization logic

### Option 2: Create Alternative Controller Contract
Create a new controller contract (e.g., `DirectOwnerController`) that:
- Works with direct owners (no BaseRegistrar requirement)
- Provides same batching and payment features as `RegistrarController`
- Can be used alongside existing `RegistrarController`

**Pros:**
- No changes to existing contracts
- Clear separation of concerns
- Can be deployed independently

**Cons:**
- Code duplication
- Two controllers to maintain
- Developer confusion about which to use

### Option 3: Documentation & Helper Contracts
- Document the two registration paths clearly
- Provide helper contracts/libraries for direct owners
- Create examples for both approaches
- Add tooling to simplify direct owner workflows

**Pros:**
- No contract changes required
- Can be implemented immediately
- Improves developer experience

**Cons:**
- Doesn't solve the atomic batching limitation
- Still requires multiple transactions
- Less convenient than Option 1 or 2

## Questions for Basenames Team

1. **Architecture Intent**: Is this architecture intentional (security/design decision)? Are there specific reasons why `RegistrarController` requires `BaseRegistrar` ownership?

2. **Future Plans**: Are there plans to support direct owners in `RegistrarController` or provide an alternative controller?

3. **Controller Contract**: Would a new controller contract for direct owners be acceptable? Would the Basenames team be open to reviewing/accepting such a contract?

4. **Recommended Approach**: What is the recommended approach for developers who want direct Registry ownership but also need atomic batching?

5. **Feature Trade-offs**: Is there a way to get tokenization/expiry features while maintaining direct Registry ownership? Or are these features fundamentally incompatible with direct ownership?

6. **Documentation**: Should we document these trade-offs more clearly in the Basenames documentation? We're happy to contribute documentation if helpful.

## Environment Details

**Network:** Base Sepolia Testnet

**Contract Addresses:**
- Registry: `0x1493b2567056c2181630115660963E13A8E32735`
- BaseRegistrar: `0xa0c70ec36c010b55e3c434d6c6ebeec50c705794`
- RegistrarController: `0x82c858CDF64b3D893Fe54962680edFDDC37e94C8`
- Resolver: `0x85C87e548091f204C2d0350b39ce1874f02197c6`

**Test Subnames:**
- `eros006.scenius.basetest.eth` - Successfully registered with all records
- `eros007.scenius.basetest.eth` - Successfully registered with all records

## Additional Context

We've built a working implementation that:
- ✅ Successfully creates subnames via `Registry.setSubnodeRecord()`
- ✅ Sets all ENS records (address + 10 text records) via batched Safe transactions
- ✅ Verifies all records are set correctly
- ✅ Handles gas estimation properly (fixed GS025 errors)

However, we believe the developer experience could be significantly improved if direct owners could use a controller-like interface for atomic batching.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.