Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add renewPoH method #117

Merged
merged 2 commits into from
May 2, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ error MaxCommitmentAgeTooHigh();
error PohVerificationFailed(address owner);
error OwnerAlreadyRegistered(address owner);
error SenderNotOwner(address owner, address sender);
error NotInGracePeriod(uint256 current, uint256 expiry);
error WrongPohRegistrationDuration(uint256 duration);

/**
Expand All @@ -51,6 +52,7 @@ contract ETHRegistrarController is
/// @dev Registration through POH is fixed to a 3 years duration
uint256 public constant POH_REGISTRATION_DURATION = 1 days * 365 * 3;
uint64 private constant MAX_EXPIRY = type(uint64).max;
uint256 public constant GRACE_PERIOD = 90 days;
BaseRegistrarImplementation immutable base;
IPriceOracle public immutable prices;
uint256 public immutable minCommitmentAge;
Expand Down Expand Up @@ -97,6 +99,8 @@ contract ETHRegistrarController is
uint256 expires
);

event NameRenewedPoh(string name, bytes32 indexed label, uint256 expires);

/**
* @notice Create registrar for the base domain passed in parameter.
* @param _base Base registrar address.
Expand Down Expand Up @@ -402,6 +406,41 @@ contract ETHRegistrarController is
emit NameRenewed(name, labelhash, msg.value, expires);
}

/**
* @notice Same as renew method except that it uses the user's POH to renew for free
* @dev Can only renew after the GRACE_PERIOD has started
* @param name to renew
* @param signature POH of the owner to renew
*/
function renewPoh(string calldata name, bytes memory signature) external {
bytes32 labelhash = keccak256(bytes(name));
bytes32 nodehash = keccak256(abi.encodePacked(baseNode, labelhash));

(address currentOwner, , ) = nameWrapper.getData(uint256(nodehash));

// The sender of the transaction needs to be the current owner of the name
if (msg.sender != currentOwner) {
revert SenderNotOwner(currentOwner, msg.sender);
}

// Check that the signature sent is valid, this is the reference for an address to have a valid PoH
if (!pohVerifier.verify(signature, currentOwner)) {
revert PohVerificationFailed(currentOwner);
}

uint256 tokenId = uint256(labelhash);
uint256 currentExpiry = base.nameExpires(tokenId);

// Renewal using POH can only occurs within the GRACE_PERIOD
if (block.timestamp < (currentExpiry - GRACE_PERIOD)) {
revert NotInGracePeriod(block.timestamp, currentExpiry);
}

uint256 expires = nameWrapper.renew(tokenId, POH_REGISTRATION_DURATION);

emit NameRenewedPoh(name, labelhash, expires);
}

function withdraw() public {
payable(owner()).transfer(address(this).balance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,164 @@ contract('ETHRegistrarController', function () {
).to.equal(86400)
})

it('should allow token owners to renew a name for free using POH', async () => {
const name = 'newname'
const duration = 3 * 365 * 24 * 60 * 60 // 3 years
const secret = ethers.utils.formatBytes32String('secret')
const human = signers[0].address
const signature = ethers.utils.hexlify(ethers.utils.randomBytes(65)) // Mock signature

// Generate a commitment for the registration
const commitment = await controllerPoh.makeCommitment(
name,
human,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
)

// Commit the registration
await controllerPoh.commit(commitment)

// Advance time to satisfy the minCommitmentAge requirement
await ethers.provider.send('evm_increaseTime', [600]) // Increase time by 600 seconds
await ethers.provider.send('evm_mine') // Mine the next block

// Perform the registration using registerPoh
const tx = await controllerPoh.registerPoh(
name,
human,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
signature,
)

// Wait for the transaction to be mined
await tx.wait()

await ethers.provider.send('evm_increaseTime', [duration])
await ethers.provider.send('evm_mine')

var nodehash = namehash(`newname.${BASE_DOMAIN_STR}`)
const [, fuses, fuseExpiry] = await nameWrapper.getData(nodehash)

var expires = await baseRegistrar.nameExpires(sha3('newname'))

await controllerPoh.renewPoh('newname', signature)
var newExpires = await baseRegistrar.nameExpires(sha3('newname'))
const [, newFuses, newFuseExpiry] = await nameWrapper.getData(nodehash)
expect(newExpires.toNumber() - expires.toNumber()).to.equal(duration)
expect(newFuseExpiry.toNumber() - fuseExpiry.toNumber()).to.equal(duration)
expect(newFuses).to.equal(fuses)
})

it('should not allow renewing a domain that does not belong to the user using POH', async () => {
const name = 'newname'
const duration = 3 * 365 * 24 * 60 * 60 // 3 years
const secret = ethers.utils.formatBytes32String('secret')
const signer1 = signers[0]
const signer2 = signers[1]
const signature = ethers.utils.hexlify(ethers.utils.randomBytes(65)) // Mock signature

// Generate a commitment for the registration
const commitment = await controllerPoh.connect(signer2).makeCommitment(
name,
signer2.address,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
)

// Commit the registration
await controllerPoh.connect(signer2).commit(commitment)

// Advance time to satisfy the minCommitmentAge requirement
await ethers.provider.send('evm_increaseTime', [600]) // Increase time by 600 seconds
await ethers.provider.send('evm_mine') // Mine the next block

// Perform the registration using registerPoh
const tx = await controllerPoh.connect(signer2).registerPoh(
name,
signer2.address,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
signature,
)

// Wait for the transaction to be mined
await tx.wait()

await ethers.provider.send('evm_increaseTime', [duration])
await ethers.provider.send('evm_mine')

await expect(
controllerPoh.renewPoh('newname', signature),
).to.be.revertedWith(
`'SenderNotOwner("${signer2.address}", "${signer1.address}")'`,
)
})

it('should not allow renewing a domain that does not belong to the user using POH', async () => {
const name = 'newname'
const duration = 3 * 365 * 24 * 60 * 60 // 3 years
const secret = ethers.utils.formatBytes32String('secret')
const signer1 = signers[0]
const signature = ethers.utils.hexlify(ethers.utils.randomBytes(65)) // Mock signature

// Generate a commitment for the registration
const commitment = await controllerPoh.makeCommitment(
name,
signer1.address,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
)

// Commit the registration
await controllerPoh.commit(commitment)

// Advance time to satisfy the minCommitmentAge requirement
await ethers.provider.send('evm_increaseTime', [600]) // Increase time by 600 seconds
await ethers.provider.send('evm_mine') // Mine the next block

// Perform the registration using registerPoh
const tx = await controllerPoh.registerPoh(
name,
signer1.address,
duration,
secret,
ethers.constants.AddressZero, // resolver address, using zero address for simplicity
[],
false,
0,
signature,
)

// Wait for the transaction to be mined
await tx.wait()

await expect(
controllerPoh.renewPoh('newname', signature),
).to.be.revertedWith(`NotInGracePeriod`)
})

it('non wrapped names can renew', async () => {
const label = 'newname'
const tokenId = sha3(label)
Expand Down