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

Modify transfer in DomainToken #119

Open
wants to merge 20 commits into
base: zns-zchain-final
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 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
10 changes: 8 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
version: 2.1

orbs:
node: circleci/node@4.7.0
node: circleci/node@6.1.0
codecov: codecov/[email protected]

defaults: &defaults
working_directory: ~/repo
docker:
- image: cimg/node:18.20.3
- image: cimg/node:20.17.0
auth:
username: $DOCKER_HUB_USERNAME
password: $DOCKER_HUB_PASSWORD
- image: mongo:7.0.0-rc5-jammy
auth:
username: $DOCKER_HUB_USERNAME
password: $DOCKER_HUB_PASSWORD

jobs:
test and coverage:
Expand Down
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"files": ["src/**/*.ts", "test/**/*.ts", "./*.ts"],
"extends": ["@zero-tech/eslint-config-cpt/node-ts/.eslintrc.json"],
"rules": {
"jsdoc/newline-after-description": "off",
"no-unused-expressions": "off",
"no-console": "off",
"no-shadow": "warn",
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ docker*.tgz

# We don't ever use the generated manifests
.openzeppelin

# Don't source control log files from deployment
deploy-*.log
4 changes: 4 additions & 0 deletions contracts/access/IZNSAccessController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ interface IZNSAccessController is IAccessControl {

function checkRegistrar(address account) external view;

function checkDomainToken(address account) external view;

function isAdmin(address account) external view returns (bool);

function isRegistrar(address account) external view returns (bool);

function isDomainToken(address account) external view returns (bool);

function isGovernor(address account) external view returns (bool);

function isExecutor(address account) external view returns (bool);
Expand Down
10 changes: 10 additions & 0 deletions contracts/access/ZNSAccessController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ contract ZNSAccessController is AccessControl, ZNSRoles, IZNSAccessController {
_setRoleAdmin(GOVERNOR_ROLE, GOVERNOR_ROLE);
// all of the admins control registrar
_setRoleAdmin(REGISTRAR_ROLE, ADMIN_ROLE);
// all of the admins control domain token
_setRoleAdmin(DOMAIN_TOKEN_ROLE, ADMIN_ROLE);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.... this will require us to upgrade every single contract in ZNS for this change to work? is it possible to do this in a less invasive way?

}

// ** Access Validators **
Expand All @@ -54,6 +56,10 @@ contract ZNSAccessController is AccessControl, ZNSRoles, IZNSAccessController {
_checkRole(REGISTRAR_ROLE, account);
}

function checkDomainToken(address account) external view override {
_checkRole(DOMAIN_TOKEN_ROLE, account);
}

// "is...()" functions return a boolean
function isAdmin(address account) external view override returns (bool) {
return hasRole(ADMIN_ROLE, account);
Expand All @@ -63,6 +69,10 @@ contract ZNSAccessController is AccessControl, ZNSRoles, IZNSAccessController {
return hasRole(REGISTRAR_ROLE, account);
}

function isDomainToken(address account) external view override returns (bool) {
return hasRole(DOMAIN_TOKEN_ROLE, account);
}

function isGovernor(address account) external view override returns (bool) {
return hasRole(GOVERNOR_ROLE, account);
}
Expand Down
5 changes: 5 additions & 0 deletions contracts/access/ZNSRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ abstract contract ZNSRoles {
*/
bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");

/**
* @notice This role is here specifically for the ZNSDomainToken.sol contract
*/
bytes32 public constant DOMAIN_TOKEN_ROLE = keccak256("DOMAIN_TOKEN_ROLE");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just use Registrar role for this? this way we don't need to upgrade all contracts just for this string...
thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually... we may have to upgrade all contracts anyway since they all have custom errors now...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's sort of a decision on the "proper" way this should be. It would definitely be the same effect to just assign the DomainToken contract the REGISTRAR_ROLE but it isn't a registrar, so it felt improper. If we have to upgrade regardless it might make sense to leave it. Let me know what you think


/**
* @notice Executor can be here to future proof, if we need a new role
* so we don't have to upgrade all contracts
Expand Down
3 changes: 2 additions & 1 deletion contracts/registry/ZNSRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry {
) external override {
if (
msg.sender != records[domainHash].owner &&
!accessController.isRegistrar(msg.sender)
!accessController.isRegistrar(msg.sender) &&
!accessController.isDomainToken(msg.sender)
) revert NotAuthorizedForDomain(msg.sender, domainHash);

_setDomainOwner(domainHash, owner);
Expand Down
10 changes: 8 additions & 2 deletions contracts/token/IZNSDomainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity 0.8.26;
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC2981 } from "@openzeppelin/contracts/interfaces/IERC2981.sol";


interface IZNSDomainToken is IERC2981, IERC721 {

/**
Expand All @@ -26,12 +25,15 @@ interface IZNSDomainToken is IERC2981, IERC721 {
*/
event TokenURISet(uint256 indexed tokenId, string indexed tokenURI);

error CallerNotOwner();

function initialize(
address accessController,
string calldata tokenName,
string calldata tokenSymbol,
address defaultRoyaltyReceiver,
uint96 defaultRoyaltyFraction
uint96 defaultRoyaltyFraction,
address registry
) external;

function totalSupply() external view returns (uint256);
Expand All @@ -55,11 +57,15 @@ interface IZNSDomainToken is IERC2981, IERC721 {

function setDefaultRoyalty(address receiver, uint96 royaltyFraction) external;

function transferTokenFrom(address from, address to, uint256 tokenId) external;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make a more clear name?...
idk... "changeTokenOwner()` or something... "transferTokenFrom" is kind of a good name, but may be a bit confusing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt making it as close to the regular transferFrom was best, so transferTokenFrom made sense because it was transferring just the token, but maybe setTokenOwner or updateTokenOwner to match verbiage from the registry would be better


function setTokenRoyalty(
uint256 tokenId,
address receiver,
uint96 royaltyFraction
) external;

function setRegistry(address registry_) external;

function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
48 changes: 47 additions & 1 deletion contracts/token/ZNSDomainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ pragma solidity 0.8.26;

import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { ERC2981Upgradeable } from "@openzeppelin/contracts-upgradeable/token/common/ERC2981Upgradeable.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import { ERC721URIStorageUpgradeable }
from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import { IZNSDomainToken } from "./IZNSDomainToken.sol";
import { ARegistryWired } from "../registry/ARegistryWired.sol";
import { AAccessControlled } from "../access/AAccessControlled.sol";


Expand All @@ -20,6 +23,7 @@ contract ZNSDomainToken is
ERC721URIStorageUpgradeable,
ERC2981Upgradeable,
UUPSUpgradeable,
ARegistryWired,
IZNSDomainToken {

/**
Expand Down Expand Up @@ -51,11 +55,13 @@ contract ZNSDomainToken is
string memory name_,
string memory symbol_,
address defaultRoyaltyReceiver,
uint96 defaultRoyaltyFraction
uint96 defaultRoyaltyFraction,
address registry_
) external override initializer {
__ERC721_init(name_, symbol_);
_setAccessController(accessController_);
_setDefaultRoyalty(defaultRoyaltyReceiver, defaultRoyaltyFraction);
_setRegistry(registry_);
}

/**
Expand Down Expand Up @@ -160,6 +166,15 @@ contract ZNSDomainToken is
emit TokenRoyaltySet(tokenId, royaltyFraction);
}

/**
* @notice Setter function for the `ZNSRegistry` address in state.
* Only ADMIN in `ZNSAccessController` can call this function.
* @param registry_ Address of the `ZNSRegistry` contract
*/
function setRegistry(address registry_) public override(ARegistryWired, IZNSDomainToken) onlyAdmin {
_setRegistry(registry_);
}

/**
* @notice To allow for user extension of the protocol we have to
* enable checking acceptance of new interfaces to ensure they are supported
Expand All @@ -174,6 +189,37 @@ contract ZNSDomainToken is
return super.supportsInterface(interfaceId);
}

/**
* @notice We override the standard transfer function to update the owner for both the `registry` and `token`
* This non-standard transfer is to behave similarly to the default transfer that only updates the `token`
*
* @param from Owner of the token
* @param to Address to send the token to
* @param tokenId The token being transferred
*/
function transferTokenFrom(address from, address to, uint256 tokenId) public override {
super.transferFrom(from, to, tokenId);
}

/**
* @notice Override the standard transferFrom function to update the owner for both the `registry` and `token`
*
* @dev See {IERC721-transferFrom}
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public override(ERC721Upgradeable, IERC721) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm... what about all other transfer methods? there used to be 3 at least. those will not do what we want.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messaged you about this, but yes because the other two public safeTransferFrom both route through this function it's fine to just override this one.

// Transfer the token
super.transferFrom(from, to, tokenId);

// Update the registry
// because `_transfer` already checks for `to == address(0)` we don't need to check it here
// We `encodePacked` here to ensure that any values that result in leading zeros are converted correctly
registry.updateDomainOwner(bytes32(abi.encodePacked(tokenId)), to);
}

/**
* @notice Return the baseURI
*/
Expand Down
2 changes: 0 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ subtask(TASK_TEST_RUN_MOCHA_TESTS)
// does not work properly locally or in CI, so we
// keep it commented out and uncomment when using DevNet
// locally.
// !!! Uncomment this when using Tenderly !!!
tenderly.setup({ automaticVerifications: false });

const config : HardhatUserConfig = {
solidity: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"@types/chai": "^4.3.11",
"@types/mocha": "^9.1.0",
"@types/node": "^18.15.11",
"@zero-tech/eslint-config-cpt": "0.2.7",
"@zero-tech/eslint-config-cpt": "0.2.8",
"@zero-tech/ztoken": "2.1.0",
"chai": "^4.3.10",
"eslint": "^8.37.0",
Expand Down
4 changes: 4 additions & 0 deletions src/deploy/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const REGISTRAR_ROLE = ethers.solidityPackedKeccak256(
["string"],
["REGISTRAR_ROLE"],
);
export const DOMAIN_TOKEN_ROLE = ethers.solidityPackedKeccak256(
["string"],
["DOMAIN_TOKEN_ROLE"],
);
export const EXECUTOR_ROLE = ethers.solidityPackedKeccak256(
["string"],
["EXECUTOR_ROLE"],
Expand Down
47 changes: 44 additions & 3 deletions src/deploy/missions/contracts/domain-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
BaseDeployMission,
TDeployArgs,
} from "@zero-tech/zdc";
import { ProxyKinds } from "../../constants";
import { DOMAIN_TOKEN_ROLE, ProxyKinds } from "../../constants";
import { znsNames } from "./names";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
Expand All @@ -24,7 +24,7 @@ IZNSContracts
instanceName = znsNames.domainToken.instance;

async deployArgs () : Promise<TDeployArgs> {
const { accessController } = this.campaign;
const { accessController, registry } = this.campaign;
const {
domainToken: {
name,
Expand All @@ -34,6 +34,47 @@ IZNSContracts
},
} = this.config;

return [ await accessController.getAddress(), name, symbol, defaultRoyaltyReceiver, defaultRoyaltyFraction ];
return [
await accessController.getAddress(),
name,
symbol,
defaultRoyaltyReceiver,
defaultRoyaltyFraction,
await registry.getAddress(),
];
}

async needsPostDeploy () {
const {
accessController,
domainToken,
config: { deployAdmin },
} = this.campaign;

const isDomainToken = await accessController
.connect(deployAdmin)
.isDomainToken(await domainToken.getAddress());

const msg = !isDomainToken ? "needs" : "doesn't need";

this.logger.debug(`${this.contractName} ${msg} post deploy sequence`);

return !isDomainToken;
}

async postDeploy () {
const {
accessController,
domainToken,
config: {
deployAdmin,
},
} = this.campaign;

await accessController
.connect(deployAdmin)
.grantRole(DOMAIN_TOKEN_ROLE, await domainToken.getAddress());

this.logger.debug(`${this.contractName} post deploy sequence completed`);
}
}
2 changes: 1 addition & 1 deletion src/deploy/missions/contracts/root-registrar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ IZNSContracts
return !isRegistrar;
}

async postDeploy () {
async postDeploy () { // TODO add this to ZNSDomainToken as well for perms to update registry domain
const {
accessController,
rootRegistrar,
Expand Down
Loading