From 814fe31c85f20a0055ebc0f222b80278a44ff562 Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 22 Nov 2024 11:02:42 +1100 Subject: [PATCH] coin type array + tests --- .../reverseRegistrar/IL2ReverseResolver.sol | 6 +- .../ISignatureReverseResolver.sol | 5 + .../reverseRegistrar/L2ReverseResolver.sol | 12 +- .../SignatureReverseResolver.sol | 15 +- .../reverseRegistrar/TestL2ReverseResolver.ts | 710 ++++++++++++++++-- .../TestL2ReverseResolverWithMigration.ts | 10 +- 6 files changed, 676 insertions(+), 82 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseResolver.sol b/contracts/reverseRegistrar/IL2ReverseResolver.sol index 1df761a1..bc38f9e7 100644 --- a/contracts/reverseRegistrar/IL2ReverseResolver.sol +++ b/contracts/reverseRegistrar/IL2ReverseResolver.sol @@ -25,16 +25,18 @@ interface IL2ReverseResolver { /// @notice Sets the `name()` record for the reverse ENS record associated with /// the contract provided that is owned with `Ownable`. - /// @param contractAddr The address of the contract to set the name for + /// @param contractAddr The address of the contract to set the name for (implementing Ownable) /// @param owner The owner of the contract (via Ownable) /// @param name The name to set + /// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract /// @param signatureExpiry The expiry of the signature /// @param signature The signature of an address that will return true on isValidSignature for the owner /// @return The ENS node hash of the reverse record - function setNameForAddrWithSignatureAndOwnable( + function setNameForOwnableWithSignature( address contractAddr, address owner, string calldata name, + uint256[] memory coinTypes, uint256 signatureExpiry, bytes calldata signature ) external returns (bytes32); diff --git a/contracts/reverseRegistrar/ISignatureReverseResolver.sol b/contracts/reverseRegistrar/ISignatureReverseResolver.sol index 1c2897c6..08876270 100644 --- a/contracts/reverseRegistrar/ISignatureReverseResolver.sol +++ b/contracts/reverseRegistrar/ISignatureReverseResolver.sol @@ -3,6 +3,9 @@ pragma solidity ^0.8.4; /// @notice Interface for the signature reverse resolver interface ISignatureReverseResolver { + /// @notice Thrown when the coin type is not found in the provided array + error CoinTypeNotFound(); + /// @notice Emitted when the name of a reverse record is changed. /// @param addr The address of the reverse record /// @param node The ENS node hash of the reverse record @@ -13,12 +16,14 @@ interface ISignatureReverseResolver { /// the addr provided account using a signature. /// @param addr The address to set the name for /// @param name The name of the reverse record + /// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract /// @param signatureExpiry Date when the signature expires /// @param signature The signature from the addr /// @return The ENS node hash of the reverse record function setNameForAddrWithSignature( address addr, string calldata name, + uint256[] calldata coinTypes, uint256 signatureExpiry, bytes calldata signature ) external returns (bytes32); diff --git a/contracts/reverseRegistrar/L2ReverseResolver.sol b/contracts/reverseRegistrar/L2ReverseResolver.sol index 3584b89a..92c00c74 100644 --- a/contracts/reverseRegistrar/L2ReverseResolver.sol +++ b/contracts/reverseRegistrar/L2ReverseResolver.sol @@ -44,27 +44,27 @@ contract L2ReverseResolver is } /// @inheritdoc IL2ReverseResolver - function setNameForAddrWithSignatureAndOwnable( + function setNameForOwnableWithSignature( address contractAddr, address owner, string calldata name, + uint256[] memory coinTypes, uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { + _validateCoinTypes(coinTypes); bytes32 node = _getNamehash(contractAddr); // Follow ERC191 version 0 https://eips.ethereum.org/EIPS/eip-191 bytes32 message = keccak256( abi.encodePacked( address(this), - IL2ReverseResolver - .setNameForAddrWithSignatureAndOwnable - .selector, + IL2ReverseResolver.setNameForOwnableWithSignature.selector, name, contractAddr, owner, - signatureExpiry, - coinType + coinTypes, + signatureExpiry ) ).toEthSignedMessageHash(); diff --git a/contracts/reverseRegistrar/SignatureReverseResolver.sol b/contracts/reverseRegistrar/SignatureReverseResolver.sol index c8b57613..67034992 100644 --- a/contracts/reverseRegistrar/SignatureReverseResolver.sol +++ b/contracts/reverseRegistrar/SignatureReverseResolver.sol @@ -46,9 +46,11 @@ contract SignatureReverseResolver is ISignatureReverseResolver, ERC165 { function setNameForAddrWithSignature( address addr, string calldata name, + uint256[] memory coinTypes, uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { + _validateCoinTypes(coinTypes); bytes32 node = _getNamehash(addr); // Follow ERC191 version 0 https://eips.ethereum.org/EIPS/eip-191 @@ -58,8 +60,8 @@ contract SignatureReverseResolver is ISignatureReverseResolver, ERC165 { ISignatureReverseResolver.setNameForAddrWithSignature.selector, name, addr, - signatureExpiry, - coinType + coinTypes, + signatureExpiry ) ).toEthSignedMessageHash(); @@ -79,6 +81,15 @@ contract SignatureReverseResolver is ISignatureReverseResolver, ERC165 { emit NameChanged(addr, node, newName); } + /// @dev Ensures the coin type for the contract is included in the provided array + function _validateCoinTypes(uint256[] memory coinTypes) internal view { + for (uint256 i = 0; i < coinTypes.length; i++) { + if (coinTypes[i] == coinType) return; + } + + revert CoinTypeNotFound(); + } + /// @inheritdoc ISignatureReverseResolver function name(bytes32 node) public view returns (string memory) { return names[node]; diff --git a/test/reverseRegistrar/TestL2ReverseResolver.ts b/test/reverseRegistrar/TestL2ReverseResolver.ts index dfee3a53..10dcc7a2 100644 --- a/test/reverseRegistrar/TestL2ReverseResolver.ts +++ b/test/reverseRegistrar/TestL2ReverseResolver.ts @@ -1,3 +1,4 @@ +import { evmChainIdToCoinType } from '@ensdomains/address-encoder/utils' import { loadFixture } from '@nomicfoundation/hardhat-toolbox-viem/network-helpers.js' import { expect } from 'chai' import hre from 'hardhat' @@ -11,9 +12,12 @@ import { type Address, type Hex, } from 'viem' +import { optimism } from 'viem/chains' +import { getReverseNamespace } from '../fixtures/getReverseNode.js' import { shouldSupportInterfaces } from '../wrapper/SupportsInterface.behaviour.js' -const coinType = 123n +const coinType = evmChainIdToCoinType(optimism.id) +const reverseNamespace = getReverseNamespace({ chainId: optimism.id }) async function fixture() { const accounts = await hre.viem @@ -21,21 +25,25 @@ async function fixture() { .then((clients) => clients.map((c) => c.account)) const l2ReverseResolver = await hre.viem.deployContract('L2ReverseResolver', [ - namehash('optimism.reverse'), + namehash(reverseNamespace), coinType, ]) - const mockSmartContractWallet = await hre.viem.deployContract( + const mockSmartContractAccount = await hre.viem.deployContract( 'MockSmartContractWallet', [accounts[0].address], ) - const mockOwnable = await hre.viem.deployContract('MockOwnable', [ - mockSmartContractWallet.address, + const mockOwnableSca = await hre.viem.deployContract('MockOwnable', [ + mockSmartContractAccount.address, + ]) + const mockOwnableEoa = await hre.viem.deployContract('MockOwnable', [ + accounts[0].address, ]) return { l2ReverseResolver, - mockSmartContractWallet, - mockOwnable, + mockSmartContractAccount, + mockOwnableSca, + mockOwnableEoa, accounts, } } @@ -45,24 +53,66 @@ const createMessageHash = ({ functionSelector, name, address, + coinTypes, signatureExpiry, }: { contractAddress: Address functionSelector: Hex name: string address: Address + coinTypes: bigint[] signatureExpiry: bigint }) => keccak256( encodePacked( - ['address', 'bytes4', 'string', 'address', 'uint256', 'uint256'], + ['address', 'bytes4', 'string', 'address', 'uint256[]', 'uint256'], [ contractAddress, functionSelector, name, address, + coinTypes, + signatureExpiry, + ], + ), + ) + +const createMessageHashForOwnable = ({ + contractAddress, + functionSelector, + name, + targetOwnableAddress, + ownerAddress, + coinTypes, + signatureExpiry, +}: { + contractAddress: Address + functionSelector: Hex + name: string + targetOwnableAddress: Address + ownerAddress: Address + coinTypes: bigint[] + signatureExpiry: bigint +}) => + keccak256( + encodePacked( + [ + 'address', + 'bytes4', + 'string', + 'address', + 'address', + 'uint256[]', + 'uint256', + ], + [ + contractAddress, + functionSelector, + name, + targetOwnableAddress, + ownerAddress, + coinTypes, signatureExpiry, - coinType, ], ), ) @@ -148,6 +198,7 @@ describe('L2ReverseResolver', () => { functionSelector, name, address: accounts[0].address, + coinTypes: [coinType], signatureExpiry, }) const signature = await walletClient.signMessage({ @@ -176,7 +227,7 @@ describe('L2ReverseResolver', () => { } = await loadFixture(setNameForAddrWithSignatureFixture) await l2ReverseResolver.write.setNameForAddrWithSignature( - [accounts[0].address, name, signatureExpiry, signature], + [accounts[0].address, name, [coinType], signatureExpiry, signature], { account: accounts[1] }, ) @@ -196,13 +247,58 @@ describe('L2ReverseResolver', () => { await expect(l2ReverseResolver) .write( 'setNameForAddrWithSignature', - [accounts[0].address, name, signatureExpiry, signature], + [accounts[0].address, name, [coinType], signatureExpiry, signature], { account: accounts[1] }, ) .toEmitEvent('NameChanged') .withArgs(getAddress(accounts[0].address), node, name) }) + it('allows SCA signatures', async () => { + const { + l2ReverseResolver, + name, + signatureExpiry, + functionSelector, + accounts, + mockSmartContractAccount, + walletClient, + } = await loadFixture(setNameForAddrWithSignatureFixture) + + const node = await l2ReverseResolver.read.node([ + mockSmartContractAccount.address, + ]) + + const messageHash = createMessageHash({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + address: mockSmartContractAccount.address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForAddrWithSignature', + [ + mockSmartContractAccount.address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[1] }, + ) + .toEmitEvent('NameChanged') + .withArgs(getAddress(mockSmartContractAccount.address), node, name) + + await expect(l2ReverseResolver.read.name([node])).resolves.toBe(name) + }) + it('reverts if signature parameters do not match', async () => { const { l2ReverseResolver, @@ -232,7 +328,7 @@ describe('L2ReverseResolver', () => { await expect(l2ReverseResolver) .write( 'setNameForAddrWithSignature', - [accounts[0].address, name, signatureExpiry, signature], + [accounts[0].address, name, [coinType], signatureExpiry, signature], { account: accounts[1] }, ) .toBeRevertedWithCustomError('InvalidSignature') @@ -254,6 +350,7 @@ describe('L2ReverseResolver', () => { functionSelector, name, address: accounts[0].address, + coinTypes: [coinType], signatureExpiry, }) const signature = await walletClient.signMessage({ @@ -263,7 +360,7 @@ describe('L2ReverseResolver', () => { await expect(l2ReverseResolver) .write( 'setNameForAddrWithSignature', - [accounts[0].address, name, signatureExpiry, signature], + [accounts[0].address, name, [coinType], signatureExpiry, signature], { account: accounts[1] }, ) .toBeRevertedWithCustomError('SignatureExpired') @@ -286,6 +383,7 @@ describe('L2ReverseResolver', () => { functionSelector, name, address: accounts[0].address, + coinTypes: [coinType], signatureExpiry, }) const signature = await walletClient.signMessage({ @@ -295,26 +393,121 @@ describe('L2ReverseResolver', () => { await expect(l2ReverseResolver) .write( 'setNameForAddrWithSignature', - [accounts[0].address, name, signatureExpiry, signature], + [accounts[0].address, name, [coinType], signatureExpiry, signature], { account: accounts[1] }, ) .toBeRevertedWithCustomError('SignatureExpiryTooHigh') }) + + it('allows unrelated coin types in array', async () => { + const { + l2ReverseResolver, + name, + node, + signatureExpiry, + functionSelector, + accounts, + walletClient, + } = await loadFixture(setNameForAddrWithSignatureFixture) + + const coinTypes = [34384n, 54842344n, 3498283n, coinType] + + const messageHash = createMessageHash({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + address: accounts[0].address, + signatureExpiry, + coinTypes, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await l2ReverseResolver.write.setNameForAddrWithSignature( + [accounts[0].address, name, coinTypes, signatureExpiry, signature], + { account: accounts[1] }, + ) + + await expect(l2ReverseResolver.read.name([node])).resolves.toBe(name) + }) + it('reverts if coin type is not in array', async () => { + const { + l2ReverseResolver, + name, + signatureExpiry, + functionSelector, + accounts, + walletClient, + } = await loadFixture(setNameForAddrWithSignatureFixture) + + const coinTypes = [34384n, 54842344n, 3498283n] + + const messageHash = createMessageHash({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + address: accounts[0].address, + signatureExpiry, + coinTypes, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForAddrWithSignature', + [accounts[0].address, name, coinTypes, signatureExpiry, signature], + { account: accounts[1] }, + ) + .toBeRevertedWithCustomError('CoinTypeNotFound') + }) + it('reverts if array is empty', async () => { + const { + l2ReverseResolver, + name, + signatureExpiry, + functionSelector, + accounts, + walletClient, + } = await loadFixture(setNameForAddrWithSignatureFixture) + + const coinTypes = [] as bigint[] + + const messageHash = createMessageHash({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + address: accounts[0].address, + signatureExpiry, + coinTypes, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForAddrWithSignature', + [accounts[0].address, name, coinTypes, signatureExpiry, signature], + { account: accounts[1] }, + ) + .toBeRevertedWithCustomError('CoinTypeNotFound') + }) }) - describe('setNameForAddrWithSignatureAndOwnable', () => { - async function setNameForAddrWithSignatureAndOwnableFixture() { + describe('setNameForOwnableWithSignature', () => { + async function setNameForOwnableWithSignatureFixture() { const initial = await loadFixture(fixture) - const { l2ReverseResolver, mockOwnable, mockSmartContractWallet } = - initial + const { l2ReverseResolver } = initial const name = 'ownable.eth' - const node = await l2ReverseResolver.read.node([mockOwnable.address]) const functionSelector = toFunctionSelector( l2ReverseResolver.abi.find( (f) => f.type === 'function' && - f.name === 'setNameForAddrWithSignatureAndOwnable', + f.name === 'setNameForOwnableWithSignature', ) as AbiFunction, ) @@ -324,97 +517,472 @@ describe('L2ReverseResolver', () => { .then((b) => b.timestamp) const signatureExpiry = blockTimestamp + 3600n - const messageHash = keccak256( - encodePacked( + const [walletClient] = await hre.viem.getWalletClients() + + return { + ...initial, + name, + functionSelector, + signatureExpiry, + walletClient, + } + } + + it('allows an EOA to sign a message to claim the address of a contract it owns via Ownable', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const node = await l2ReverseResolver.read.node([mockOwnableEoa.address]) + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toEmitEvent('NameChanged') + .withArgs(getAddress(mockOwnableEoa.address), node, name) + + await expect(l2ReverseResolver.read.name([node])).resolves.toBe(name) + }) + + it('allows an SCA to sign a message to claim the address of a contract it owns via Ownable', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + mockOwnableSca, + mockSmartContractAccount, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const node = await l2ReverseResolver.read.node([mockOwnableSca.address]) + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableSca.address, + ownerAddress: mockSmartContractAccount.address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableSca.address, + mockSmartContractAccount.address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toEmitEvent('NameChanged') + .withArgs(getAddress(mockOwnableSca.address), node, name) + + await expect(l2ReverseResolver.read.name([node])).resolves.toBe(name) + }) + + it('reverts if the owner address is not the owner of the contract', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + mockOwnableEoa, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + const [, walletClient] = await hre.viem.getWalletClients() + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[1].address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', [ - 'address', - 'bytes4', - 'string', - 'address', - 'address', - 'uint256', - 'uint256', + mockOwnableEoa.address, + accounts[1].address, + name, + [coinType], + signatureExpiry, + signature, ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('NotOwnerOfContract') + }) + + it('reverts if the target address is not a contract', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: accounts[2].address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + accounts[2].address, + accounts[0].address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('NotOwnerOfContract') + }) + + it('reverts if the target address does not implement Ownable', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: l2ReverseResolver.address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', [ l2ReverseResolver.address, - functionSelector, + accounts[0].address, name, - mockOwnable.address, - mockSmartContractWallet.address, + [coinType], signatureExpiry, - coinType, + signature, ], - ), - ) + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('NotOwnerOfContract') + }) - const [walletClient] = await hre.viem.getWalletClients() + it('reverts if the signature is invalid', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], + signatureExpiry: 0n, + }) const signature = await walletClient.signMessage({ message: { raw: messageHash }, }) - return { - ...initial, + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('InvalidSignature') + }) + + it('reverts if expiry date is too low', async () => { + const { + l2ReverseResolver, name, - node, functionSelector, + accounts, + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const signatureExpiry = 0n + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], signatureExpiry, - signature, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('SignatureExpired') + }) + + it('reverts if expiry date is too high', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + accounts, + mockOwnableEoa, walletClient, - } - } + signatureExpiry: oldSignatureExpiry, + } = await loadFixture(setNameForOwnableWithSignatureFixture) - it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { + const signatureExpiry = oldSignatureExpiry + 86401n + + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes: [coinType], + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + [coinType], + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('SignatureExpiryTooHigh') + }) + + it('allows unrelated coin types in array', async () => { const { l2ReverseResolver, name, - node, + functionSelector, signatureExpiry, - signature, accounts, - mockOwnable, - mockSmartContractWallet, - } = await loadFixture(setNameForAddrWithSignatureAndOwnableFixture) - - await l2ReverseResolver.write.setNameForAddrWithSignatureAndOwnable( - [ - mockOwnable.address, - mockSmartContractWallet.address, - name, - signatureExpiry, - signature, - ], - { account: accounts[1] }, - ) + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const coinTypes = [34384n, 54842344n, 3498283n, coinType] + const node = await l2ReverseResolver.read.node([mockOwnableEoa.address]) + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes, + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + coinTypes, + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toEmitEvent('NameChanged') + .withArgs(getAddress(mockOwnableEoa.address), node, name) await expect(l2ReverseResolver.read.name([node])).resolves.toBe(name) }) - it('event NameChanged is emitted', async () => { + it('reverts if coin type is not in array', async () => { const { l2ReverseResolver, name, - node, + functionSelector, signatureExpiry, - signature, accounts, - mockOwnable, - mockSmartContractWallet, - } = await loadFixture(setNameForAddrWithSignatureAndOwnableFixture) + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const coinTypes = [34384n, 54842344n, 3498283n] + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes, + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) await expect(l2ReverseResolver) .write( - 'setNameForAddrWithSignatureAndOwnable', + 'setNameForOwnableWithSignature', [ - mockOwnable.address, - mockSmartContractWallet.address, + mockOwnableEoa.address, + accounts[0].address, name, + coinTypes, signatureExpiry, signature, ], - { account: accounts[1] }, + { account: accounts[9] }, ) - .toEmitEvent('NameChanged') - .withArgs(getAddress(mockOwnable.address), node, name) + .toBeRevertedWithCustomError('CoinTypeNotFound') + }) + + it('reverts if array is empty', async () => { + const { + l2ReverseResolver, + name, + functionSelector, + signatureExpiry, + accounts, + mockOwnableEoa, + walletClient, + } = await loadFixture(setNameForOwnableWithSignatureFixture) + + const coinTypes = [] as bigint[] + const messageHash = createMessageHashForOwnable({ + contractAddress: l2ReverseResolver.address, + functionSelector, + name, + targetOwnableAddress: mockOwnableEoa.address, + ownerAddress: accounts[0].address, + coinTypes, + signatureExpiry, + }) + const signature = await walletClient.signMessage({ + message: { raw: messageHash }, + }) + + await expect(l2ReverseResolver) + .write( + 'setNameForOwnableWithSignature', + [ + mockOwnableEoa.address, + accounts[0].address, + name, + coinTypes, + signatureExpiry, + signature, + ], + { account: accounts[9] }, + ) + .toBeRevertedWithCustomError('CoinTypeNotFound') }) }) }) diff --git a/test/reverseRegistrar/TestL2ReverseResolverWithMigration.ts b/test/reverseRegistrar/TestL2ReverseResolverWithMigration.ts index a25adf18..d3cc586d 100644 --- a/test/reverseRegistrar/TestL2ReverseResolverWithMigration.ts +++ b/test/reverseRegistrar/TestL2ReverseResolverWithMigration.ts @@ -40,7 +40,7 @@ async function fixture() { } describe('L2ReverseResolverWithMigration', () => { - it('should migrate names wahoo', async () => { + it('should migrate names', async () => { const { l2ReverseResolver, oldReverseResolver, accounts } = await loadFixture(fixture) @@ -58,4 +58,12 @@ describe('L2ReverseResolverWithMigration', () => { expect(newName).toBe(`name-${i}.eth`) } }) + + it('should revert if not owner', async () => { + const { l2ReverseResolver, accounts } = await loadFixture(fixture) + + await expect(l2ReverseResolver) + .write('batchSetName', [[accounts[0].address]], { account: accounts[1] }) + .toBeRevertedWithString('Ownable: caller is not the owner') + }) })