From c297e51bd7137490d28d91370e050bce202843d4 Mon Sep 17 00:00:00 2001 From: spengrah Date: Mon, 20 Mar 2023 14:46:43 -0500 Subject: [PATCH 1/2] deploy v1 --- example.env | 7 ++++++- foundry.toml | 17 ++++++++++------- script/Hats.s.sol | 19 ++++++++++++++----- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/example.env b/example.env index 34aa8af..bbd8901 100644 --- a/example.env +++ b/example.env @@ -1,8 +1,13 @@ GOERLI_RPC= +SEPOLIA_RPC= ETHEREUM_RPC= GC_RPC= POLYGON_RPC= +OPTIMISM_RPC= +ARBITRUM_RPC= export PRIVATE_KEY= ETHERSCAN_KEY= GNOSISSCAN_KEY= -POLYGONSCAN_KEY= \ No newline at end of file +POLYGONSCAN_KEY= +OPTIMISM_KEY= +ARBISCAN_KEY= \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 6ee8c9a..cfe2388 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,20 +26,23 @@ wrap_comments = false book = "./book.toml" [rpc_endpoints] -ethereum = "${ETHEREUM_RPC}" -optimism = "${OPTIMISM_RPC}" -goerli = "${GOERLI_RPC}" +arbitrum = "${ARBITRUM_RPC}" gnosis = "${GC_RPC}" -polygon = "${POLYGON_RPC}" +goerli = "${GOERLI_RPC}" local = "http://localhost:8545" +mainnet = "${ETHEREUM_RPC}" +optimism = "${OPTIMISM_RPC}" +polygon = "${POLYGON_RPC}" +sepolia = "${SEPOLIA_RPC}" [etherscan] arbitrum = {key = "${ARBISCAN_KEY}", url = "https://api.arbiscan.io/api"} goerli = {key = "${ETHERSCAN_KEY}", url = "https://api-goerli.etherscan.io/api"} -ethereum = {key = "${ETHERSCAN_KEY}", chain = "mainnet"} -optimism = {key = "${OPTIMISM_ETHERSCAN_KEY}", url = "https://api-optimistic.etherscan.io/api"} -polygon = {key = "${POLYGONSCAN_KEY}", url = "https://api.polygonscan.com/api"} gnosis = {key = "${GNOSISSCAN_KEY}", url = "https://api.gnosisscan.io/api"} +mainnet = {key = "${ETHERSCAN_KEY}", url = "https://api.etherscan.io/api"} +optimism = {key = "${OPTIMISM_KEY}", url = "https://api-optimistic.etherscan.io/api"} +sepolia = {key = "${ETHERSCAN_KEY}", url = "https://api-sepolia.etherscan.io/api"} +polygon = {key = "${POLYGONSCAN_KEY}", url = "https://api.polygonscan.com/api"} # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/Hats.s.sol b/script/Hats.s.sol index c19560c..c10d744 100644 --- a/script/Hats.s.sol +++ b/script/Hats.s.sol @@ -7,7 +7,7 @@ import { Hats } from "../src/Hats.sol"; contract DeployHats is Script { string public constant baseImageURI = "ipfs://bafybeigcimbqwfajsnhoq7fqnbdllz7kye7cpdy3adj2sob3wku2llu5bi"; - string public constant name = "Hats Protocol v1-beta1"; // increment this each deployment + string public constant name = "Hats Protocol v1"; // increment this each deployment bytes32 internal constant SALT = bytes32(abi.encode(0x4a75)); // ~ H(4) A(a) T(7) S(5) @@ -15,24 +15,33 @@ contract DeployHats is Script { uint256 privKey = vm.envUint("PRIVATE_KEY"); address deployer = vm.rememberKey(privKey); - console2.log(vm.getNonce(deployer)); + console2.log("Deployer: ", deployer); + console2.log("Deployer Nonce: ", vm.getNonce(deployer)); vm.startBroadcast(deployer); + // deploy Hats Hats hats = new Hats{ salt: SALT }(name, baseImageURI); + // mint Hats Protocol Governance tophat + hats.mintTopHat( + 0x2D785497c6C8ce3f4cCff4937D321C37e80705E8, // hatsprotocol.eth + "Hats Protocol Governance", + baseImageURI + ); + vm.stopBroadcast(); console2.log("Salt: ", vm.toString(SALT)); console2.log("Hats contract: ", address(hats)); } - // forge script script/Hats.s.sol:DeployHats -f goerli - // forge script script/Hats.s.sol:DeployHats -f goerli --broadcast --verify + // forge script script/Hats.s.sol:DeployHats -f ethereum + // forge script script/Hats.s.sol:DeployHats -f ethereum --broadcast --verify // forge script script/Hats.s.sol:DeployHats --rpc-url http://localhost:8545 --broadcast - // forge verify-contract --chain-id 5 --num-of-optimizations 10000 --watch --constructor-args $(cast abi-encode "constructor(string,string)" "Hats Protocol v1-beta1" "ipfs://bafybeigcimbqwfajsnhoq7fqnbdllz7kye7cpdy3adj2sob3wku2llu5bi") --compiler-version v0.8.17 0x96bD657Fcc04c71B47f896a829E5728415cbcAa1 src/Hats.sol:Hats $ETHERSCAN_KEY + // forge verify-contract --chain-id 1 --num-of-optimizations 10000 --watch --constructor-args $(cast abi-encode "constructor(string,string)" "Hats Protocol v1" "ipfs://bafybeigcimbqwfajsnhoq7fqnbdllz7kye7cpdy3adj2sob3wku2llu5bi") --compiler-version v0.8.17 0x850f3384829D7bab6224D141AFeD9A559d745E3D src/Hats.sol:Hats --etherscan-api-key $ETHERSCAN_KEY } contract DeployHatsAndMintTopHat is Script { From 8f1aae4c21c723c84e8e76bc6393f45f2f8cd347 Mon Sep 17 00:00:00 2001 From: spengrah Date: Mon, 20 Mar 2023 16:50:12 -0500 Subject: [PATCH 2/2] update README --- README.md | 27 +++++++++++++++++++-------- script/Hats.s.sol | 3 ++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4d4b9a9..2a0c7e1 100644 --- a/README.md +++ b/README.md @@ -147,13 +147,13 @@ To avoid confusion, Hats Protocol does not claim to be ERC1155-compliant. Instea Each Hat has several properties: * `id` - the integer identifier for the Hat, which also serves as the ERC1155-similar token id (see three paragraphs below) -* `details` - metadata about the Hat; such as a name, description, and other properties like roles and responsibilities associated with the Hat +* `details` - metadata about the Hat; such as a name, description, and other properties like roles and responsibilities associated with the Hat. Should not exceed 7,000 characters. * `maxSupply` - the maximum number of addresses that can wear the Hat at once * `admin` - the Hat that controls who can wear the Hat * `eligibility` - the address that controls eligibility criteria and whether a given wearer of the Hat is in good standing * `toggle` - the address that controls whether the Hat is active * `mutable` - whether the hat's properties can be changed by the admin -* `imageURI` - the URI for the image used in the Hat's ERC1155-similar token +* `imageURI` - the URI for the image used in the Hat's ERC1155-similar token. Should not exceed 7,000 characters. For more information on each property, refer to the detailed sections below. @@ -304,7 +304,7 @@ Changes are allowed to the following Hat properties: Additionally, mutable hats can be transferred by their admins to a different wearer. Immutable hats cannot be transferred. -#### Tophat Exception +#### TopHat Exception The only exception to the above mutability rules is for tophats, which despite being immutable are allowed to change their own `details` and `imageURI` (but not other properties). @@ -346,7 +346,7 @@ To create a batch of Hats, a DAO can call the `Hats.batchCreateHats()` function. Only a Hat's admin can mint its token to a wearer. -To mint a Hat, the Hat's max supply must not have already been reached, the target wearer must not already wear the Hat, and the target wearer must be eligible for the hat. +To mint a Hat, the Hat must be active, its max supply must not have already been reached, the target wearer must not already wear the Hat, and the target wearer must be eligible for the Hat. A Hat's admin can mint its token individually by calling `Hats.mintHat`. @@ -368,18 +368,29 @@ For these reasons, in Hats Protocol, the standard ERC1155 transfer functions &md As a replacement, Hats can be transfered by admins via `Hats.transferHat`, which emits the ERC1155 standard event `TransferSingle`. Transfer recipients must not already be wearing the hat, and must be eligible to wear the hat. -With the exception of tophats — which can always transfer themselves — only mutable Hats can be transferred. +With the exception of tophats — which can always transfer themselves — only mutable Hats can be transferred. Inactive Hats cannot be transferred. ### Hat Tree Grafting Not all Hats trees will unfurl from top down or inside out. Sometimes, new branches will form independently from the main tree, or multiple trees will form before a main tree even exists. -In these cases, Hat trees can be grafted onto other trees. This is done by the wearer of one tree's tophat linking their tophat to a hat in another tree, via `Hats.linkTopHatToTree`. This has two main effects: +In these cases, Hat trees can be grafted onto other trees. This is done via a request-approve process where the wearer of one tree's topHat requests to link their topHat to a hat in another tree, whose admin can approve if desired. This has three main effects: -1. The linked tophat loses its tophat status (i.e., `Hats.isTopHat` will return `false`) and turns into what we call a "tree root", and +1. The linked tophat loses its topHat status (i.e., `Hats.isTopHat` will return `false`) and turns into what we call a "tree root" or "linked topHat", and 2. The hat to which it is linked becomes its new admin; it is no longer its own admin +3. On linking, the linked topHat can be assigned eligibility and/or toggle modules like any other hat -Linked Hat trees can also be unlinked by the tree root from its linked admin, via `Hats.unlinkTopHatFromTree`. This causes the tree root to regain its status as a top hat and to once again become its own admin. +Linked Hat trees can also be unlinked by the tree root from its linked admin, via `Hats.unlinkTopHatFromTree`. This causes the tree root to regain its status as a top hat and to once again become its own admin. Any eligibility or toggle modules added on linking are cleared. + +⚠️ **CAUTION**: Be careful when nesting multiple Hat trees. If the nested linkages become too long, the higher level admins may lose control of the lowest level Hats because admin actions at that distance may cost-prohibitive or even exceed the gas limit. Best practice is to not attach external authorities (e.g. via token gating) to Hats in trees that are more than ~10 nested trees deep (varies by network). + +#### Relinking + +Linked topHats can be relinked to a different Hat within the same tree. This is useful for DAOs that want to reorganize their subtrees without having to go through the request and approve steps. Valid relinks must meet the following criteria in order to ensure security: + +1. The Hat wearer executing the relink is an admin of both the linked topHat and the new admin (destination) +2. The new admin (destination) is within the same local tree as the existing admin (origin), or within the tippy top hat's local tree. Tippy top hats executing a relink are not subject to these restrictions. +3. The new link does not create a circular linkage. ### Renouncing a Hat diff --git a/script/Hats.s.sol b/script/Hats.s.sol index c10d744..dda0251 100644 --- a/script/Hats.s.sol +++ b/script/Hats.s.sol @@ -23,7 +23,8 @@ contract DeployHats is Script { // deploy Hats Hats hats = new Hats{ salt: SALT }(name, baseImageURI); - // mint Hats Protocol Governance tophat + // mint Hats Protocol Governance topHat + // Note: This topHat is not connected to any protocol authorities. The protocol is fully permissionless and not upgradeable. hats.mintTopHat( 0x2D785497c6C8ce3f4cCff4937D321C37e80705E8, // hatsprotocol.eth "Hats Protocol Governance",