From 5f5d62b75e4c6b6e5a819d37a99de48851ff9aa5 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 18 Apr 2023 15:59:32 +0900 Subject: [PATCH 1/3] Parent avatar resolver and tests --- contracts/resolvers/ParentAvatarResolver.sol | 63 ++++++++++++++ test/resolvers/TestParentAvatarResolver.js | 89 ++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 contracts/resolvers/ParentAvatarResolver.sol create mode 100644 test/resolvers/TestParentAvatarResolver.js diff --git a/contracts/resolvers/ParentAvatarResolver.sol b/contracts/resolvers/ParentAvatarResolver.sol new file mode 100644 index 00000000..a67f7ebd --- /dev/null +++ b/contracts/resolvers/ParentAvatarResolver.sol @@ -0,0 +1,63 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.17 <0.9.0; + +import {ENS} from "../registry/ENS.sol"; +import {INameWrapper} from "../wrapper/INameWrapper.sol"; +import {PublicResolver} from "./PublicResolver.sol"; + +error AvatarCannotBeSetByOwner(); + +/** + * + * This resolver allows the owner of a node to set the avatar of a child node. + * + */ +contract ParentAvatarResolver is PublicResolver { + constructor( + ENS _ens, + INameWrapper wrapperAddress, + address _trustedETHController, + address _trustedReverseRegistrar + ) + PublicResolver( + _ens, + wrapperAddress, + _trustedETHController, + _trustedReverseRegistrar + ) + {} + + function setText( + bytes32 node, + string calldata key, + string calldata value + ) external override authorised(node) { + // check if key is avatar + if ( + keccak256(abi.encodePacked(key)) == + keccak256(abi.encodePacked("avatar")) + ) { + revert AvatarCannotBeSetByOwner(); + } + + _setText(node, key, value); + } + + function setAvatar( + bytes32 parentNode, + bytes32 labelhash, + string calldata value + ) external authorised(parentNode) { + bytes32 node = keccak256(abi.encodePacked(parentNode, labelhash)); + _setText(node, "avatar", value); + } + + function _setText( + bytes32 node, + string memory key, + string calldata value + ) internal { + versionable_texts[recordVersions[node]][node][key] = value; + emit TextChanged(node, key, key, value); + } +} diff --git a/test/resolvers/TestParentAvatarResolver.js b/test/resolvers/TestParentAvatarResolver.js new file mode 100644 index 00000000..2f54b419 --- /dev/null +++ b/test/resolvers/TestParentAvatarResolver.js @@ -0,0 +1,89 @@ +const ENS = artifacts.require('./registry/ENSRegistry.sol') +const NameWrapper = artifacts.require('DummyNameWrapper.sol') +const { deploy } = require('../test-utils/contracts') +const { labelhash } = require('../test-utils/ens') +const { + EMPTY_BYTES32: ROOT_NODE, + EMPTY_ADDRESS, +} = require('../test-utils/constants') + +const { expect } = require('chai') +const namehash = require('eth-ens-namehash') + +contract('PublicResolver', function (accounts) { + let node + let ens, resolver, nameWrapper + let account + let signers + + beforeEach(async () => { + signers = await ethers.getSigners() + account = await signers[0].getAddress() + node = namehash.hash('eth') + ens = await ENS.new() + nameWrapper = await NameWrapper.new() + + //setup reverse registrar + + const ReverseRegistrar = await deploy('ReverseRegistrar', ens.address) + + await ens.setSubnodeOwner(ROOT_NODE, labelhash('reverse'), account) + await ens.setSubnodeOwner( + namehash.hash('reverse'), + labelhash('addr'), + ReverseRegistrar.address, + ) + + resolver = await deploy( + 'ParentAvatarResolver', + ens.address, + nameWrapper.address, + accounts[9], // trusted contract + ReverseRegistrar.address, //ReverseRegistrar.address, + ) + + await ReverseRegistrar.setDefaultResolver(resolver.address) + + await ens.setSubnodeOwner('0x0', labelhash('eth'), accounts[0], { + from: accounts[0], + }) + }) + + describe('setText()', () => { + it('should set text', async () => { + await resolver.setText(node, 'url', 'https://example.com', { + from: accounts[0], + }) + const result = await resolver.text(node, 'url') + expect(result).to.equal('https://example.com') + }) + + it('should not be able to set avatar', async () => { + await expect( + resolver.setText(node, 'avatar', 'https://example.com', { + from: accounts[0], + }), + ).to.be.revertedWith('AvatarCannotBeSetByOwner()') + }) + }) + + describe('setAvatar()', () => { + it('should be able to set avatar as the parentOwner', async () => { + resolver.setAvatar(ROOT_NODE, labelhash('eth'), 'https://example.com', { + from: accounts[0], + }) + + const result = await resolver.text(node, 'avatar') + expect(result).to.equal('https://example.com') + }) + + it('should not able to set avatar if not the parent Owner', async () => { + ens.setSubnodeOwner('0x0', labelhash('eth'), accounts[1]) + expect( + resolver.setAvatar(ROOT_NODE, labelhash('eth'), 'https://example.com', { + from: accounts[1], + }), + ).to.be.revertedWith('AvatarCannotBeSetByOwner()') + }) + }) +}) From 2464b4a5bf87df0cfc3e80077544565b683c4d4a Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 18 Apr 2023 16:00:23 +0900 Subject: [PATCH 2/3] Edit test names --- test/resolvers/TestParentAvatarResolver.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/resolvers/TestParentAvatarResolver.js b/test/resolvers/TestParentAvatarResolver.js index 2f54b419..20206f74 100644 --- a/test/resolvers/TestParentAvatarResolver.js +++ b/test/resolvers/TestParentAvatarResolver.js @@ -2,15 +2,12 @@ const ENS = artifacts.require('./registry/ENSRegistry.sol') const NameWrapper = artifacts.require('DummyNameWrapper.sol') const { deploy } = require('../test-utils/contracts') const { labelhash } = require('../test-utils/ens') -const { - EMPTY_BYTES32: ROOT_NODE, - EMPTY_ADDRESS, -} = require('../test-utils/constants') +const { EMPTY_BYTES32: ROOT_NODE } = require('../test-utils/constants') const { expect } = require('chai') const namehash = require('eth-ens-namehash') -contract('PublicResolver', function (accounts) { +contract('Parent Avatar Resolver', function (accounts) { let node let ens, resolver, nameWrapper let account From 700134171862f75b2800e17605ae6d3563fe0d23 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 18 Apr 2023 16:36:48 +0900 Subject: [PATCH 3/3] Fix tests --- contracts/resolvers/ResolverBase.sol | 6 +++++- test/resolvers/TestParentAvatarResolver.js | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/contracts/resolvers/ResolverBase.sol b/contracts/resolvers/ResolverBase.sol index 3eb8ba73..a9b2db9c 100644 --- a/contracts/resolvers/ResolverBase.sol +++ b/contracts/resolvers/ResolverBase.sol @@ -4,13 +4,17 @@ pragma solidity >=0.8.4; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import "./profiles/IVersionableResolver.sol"; +error NotAuthorised(); + abstract contract ResolverBase is ERC165, IVersionableResolver { mapping(bytes32 => uint64) public recordVersions; function isAuthorised(bytes32 node) internal view virtual returns (bool); modifier authorised(bytes32 node) { - require(isAuthorised(node)); + if (!isAuthorised(node)) { + revert NotAuthorised(); + } _; } diff --git a/test/resolvers/TestParentAvatarResolver.js b/test/resolvers/TestParentAvatarResolver.js index 20206f74..d3bdb877 100644 --- a/test/resolvers/TestParentAvatarResolver.js +++ b/test/resolvers/TestParentAvatarResolver.js @@ -9,7 +9,7 @@ const namehash = require('eth-ens-namehash') contract('Parent Avatar Resolver', function (accounts) { let node - let ens, resolver, nameWrapper + let ens, resolver, resolver2, nameWrapper let account let signers @@ -39,6 +39,8 @@ contract('Parent Avatar Resolver', function (accounts) { ReverseRegistrar.address, //ReverseRegistrar.address, ) + resolver2 = resolver.connect(signers[1]) + await ReverseRegistrar.setDefaultResolver(resolver.address) await ens.setSubnodeOwner('0x0', labelhash('eth'), accounts[0], { @@ -76,11 +78,9 @@ contract('Parent Avatar Resolver', function (accounts) { it('should not able to set avatar if not the parent Owner', async () => { ens.setSubnodeOwner('0x0', labelhash('eth'), accounts[1]) - expect( - resolver.setAvatar(ROOT_NODE, labelhash('eth'), 'https://example.com', { - from: accounts[1], - }), - ).to.be.revertedWith('AvatarCannotBeSetByOwner()') + await expect( + resolver2.setAvatar(ROOT_NODE, labelhash('eth'), 'https://example.com'), + ).to.be.revertedWith('NotAuthorised()') }) }) })