From 01b1d53d68b86fe226994de1f355e80c44ab6ecc Mon Sep 17 00:00:00 2001 From: Julink Date: Tue, 30 Apr 2024 10:13:55 +0200 Subject: [PATCH] feat: add renewPoH method --- .../ethregistrar/ETHRegistrarController.sol | 40 +++++ .../TestEthRegistrarController.js | 158 ++++++++++++++++++ 2 files changed, 198 insertions(+) diff --git a/packages/l2-contracts/contracts/ethregistrar/ETHRegistrarController.sol b/packages/l2-contracts/contracts/ethregistrar/ETHRegistrarController.sol index ea0f71f28..414d24ed0 100644 --- a/packages/l2-contracts/contracts/ethregistrar/ETHRegistrarController.sol +++ b/packages/l2-contracts/contracts/ethregistrar/ETHRegistrarController.sol @@ -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); /** * @dev A registrar controller for registering and renewing names at fixed cost. @@ -47,7 +48,9 @@ contract ETHRegistrarController is using Address for address; uint256 public constant MIN_REGISTRATION_DURATION = 28 days; + 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; @@ -95,6 +98,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. @@ -395,6 +400,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); } diff --git a/packages/l2-contracts/test/ethregistrar/TestEthRegistrarController.js b/packages/l2-contracts/test/ethregistrar/TestEthRegistrarController.js index bbd5dd98a..b7ebad28f 100644 --- a/packages/l2-contracts/test/ethregistrar/TestEthRegistrarController.js +++ b/packages/l2-contracts/test/ethregistrar/TestEthRegistrarController.js @@ -763,6 +763,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)