Mitigation/trust-audit-spencer#97
Conversation
topocount
left a comment
There was a problem hiding this comment.
Awesome! I have a handful of suggested changes and questions that optionally could lead to a change, but this looks fantastic
| if (_hatId & uint256(type(uint224).max) == 0) return 0; | ||
| if (_hatId & uint256(type(uint208).max) == 0) return 1; | ||
| if (_hatId & uint256(type(uint192).max) == 0) return 2; | ||
| if (_hatId & uint256(type(uint176).max) == 0) return 3; | ||
| if (_hatId & uint256(type(uint160).max) == 0) return 4; | ||
| if (_hatId & uint256(type(uint144).max) == 0) return 5; | ||
| if (_hatId & uint256(type(uint128).max) == 0) return 6; | ||
| if (_hatId & uint256(type(uint112).max) == 0) return 7; | ||
| if (_hatId & uint256(type(uint96).max) == 0) return 8; | ||
| if (_hatId & uint256(type(uint80).max) == 0) return 9; | ||
| if (_hatId & uint256(type(uint64).max) == 0) return 10; | ||
| if (_hatId & uint256(type(uint48).max) == 0) return 11; | ||
| if (_hatId & uint256(type(uint32).max) == 0) return 12; | ||
| if (_hatId & uint256(type(uint16).max) == 0) return 13; |
There was a problem hiding this comment.
not sure if hardcoding these max valueswill improve runtime cost, but I'm guessing it will.
| Hats Protocol is a protocol for DAO-native roles and credentials that supports revocable delegation of authority and responsibility. | ||
|
|
||
| Hats are represented on-chain by non-transferable ERC1155 tokens. An address with a balance of a given Hat token "wears" that hat, granting them the responsibilities and authorities that have been assigned to the Hat by the DAO. | ||
| Hats are represented on-chain by non-transferable tokens that conform to the ERC1155 interface. An address with a balance of a given Hat token "wears" that hat, granting them the responsibilities and authorities that have been assigned to the Hat by the DAO. |
|
|
||
| Hats Protocol conforms fully to the ERC1155 interface. All external functions required by the [ERC1155 standard](https://eips.ethereum.org/EIPS/eip-1155) are exposed by Hats Protocol. This is how Hats can work out of the box with existing token-gating applications. | ||
|
|
||
| However, Hats Protocol is not fully compliant with the ERC1155 standard. Since Hats are not transferable by their owners (aka "wearers"), there is little need for safe transfers and the `ERC1155TokenReceiver` logic. Developers building on top of Hats Protocol should note that mints and transfers of Hats will not, for example, include calls to `onERC1155Received`. |
There was a problem hiding this comment.
The special case here is smart contracts that administer hats. However, if a smart contract is capable of interfacing with the hats contract, it's probably capable of burning or reassigning any self-assigned hats. Probably not worth documenting, just trying to be explicit here.
README.md
Outdated
| 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, and the target wearer must not already wear the Hat. | ||
| 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 not be eligible for the hat. |
There was a problem hiding this comment.
wait...shouldn't the target be eligible?
There was a problem hiding this comment.
whoops, typo — good catch!
| /// @dev Similar to getHatLevel, but does not account for linked trees | ||
| /// @param _hatId the id of the hat in question | ||
| /// @return level The local level, from 0 to 14 | ||
| function getLocalHatLevel(uint256 _hatId) public pure returns (uint32 level) { |
There was a problem hiding this comment.
sorry to be pedantic, but isn't it more self-descriptive to use getTreeHatLevel? I'm just inquiring and will leave this for your discretion
There was a problem hiding this comment.
Heh I went back and forth on this too. I was inspired by your variable names in getAdminAtLevel, and we use tree in a number of different contexts so I think Local is currently clearer all things considered.
src/Hats.sol
Outdated
| */ | ||
| if (success && returndata.length == 32) { | ||
| // check the returndata manually | ||
| uint256 uintReturndata = uint256(bytes32(returndata)); |
There was a problem hiding this comment.
I'm guessing this is cheaper then abi.decode? I'd imagine so, since it's potentially lossy and has no safety checks built in.
There was a problem hiding this comment.
actually, its ever so slightly more expensive. Changing to abi.decode...
| if (success && returndata.length == 64) { | ||
| bool standing; | ||
| (eligible, standing) = abi.decode(returndata, (bool, bool)); | ||
| // never eligible if in bad standing | ||
| if (eligible && !standing) eligible = false; | ||
| // check the returndata manually | ||
| (uint256 firstWord, uint256 secondWord) = abi.decode(returndata, (uint256, uint256)); | ||
| // returndata is valid | ||
| if (firstWord < 2 && secondWord < 2) { | ||
| standing = (secondWord == 1) ? true : false; | ||
| // never eligible if in bad standing | ||
| eligible = (standing && firstWord == 1) ? true : false; | ||
| } | ||
| // returndata is invalid | ||
| else { | ||
| eligible = !badStandings[_hatId][_wearer]; | ||
| } |
There was a problem hiding this comment.
I agree that this should be deduplicated in a helper function.
| if (success && returndata.length == 32) { | ||
| // check the returndata manually | ||
| uint256 uintReturndata = uint256(bytes32(returndata)); | ||
| // false condition | ||
| if (uintReturndata == 0) { | ||
| active = false; | ||
| // true condition | ||
| } else if (uintReturndata == 1) { | ||
| active = true; | ||
| } | ||
| // invalid condition | ||
| else { | ||
| active = _getHatStatus(_hat); | ||
| } | ||
| } else { | ||
| return _getHatStatus(_hat); | ||
| active = _getHatStatus(_hat); |
There was a problem hiding this comment.
If solidity supported some light metaprogramming, we could deduplicate this codeblock but I think it's too nuanced.
There was a problem hiding this comment.
I actually did try...
_retrieveAndProcessToggleReturnData(Hat memory _hat, uint256 _hatId, function(Hat memory) internal view returns (bool) _fallbackFunc) internal view returns (bool)
But it ended up being more expensive on execution so I stuck with the repeated code, as messy as it looks.
| } else { | ||
| if (isWearerOfHat(_user, linkedTreeAdmin)) return true; // user wears the linkedTreeAdmin | ||
|
|
||
| else return isAdminOfHat(_user, linkedTreeAdmin); // check if user is admin of linkedTreeAdmin (recursion) |
There was a problem hiding this comment.
It's crazy to me that all this extra logic saves us gas relative to an extra storage read for linkages, but here we are! Great work sorting this out. I didn't realize this, on top of the HatsIdUtils library refactor was needed, and feel bad kicking this back to you now.
There was a problem hiding this comment.
Not all all! Was fun to untangle :)
| if (isLocalTopHat(_hatId)) { | ||
| linkedTreeAdmin = linkedTreeAdmins[getTophatDomain(_hatId)]; | ||
| if (linkedTreeAdmin == 0) { | ||
| // tree is not linked | ||
| return isWearerOfHat(_user, _hatId); | ||
| } else { | ||
| // tree is linked | ||
| if (isWearerOfHat(_user, linkedTreeAdmin)) { | ||
| return true; | ||
| } // user wears the treeAdmin | ||
| else { | ||
| adminLocalHatLevel = getLocalHatLevel(linkedTreeAdmin); | ||
| _hatId = linkedTreeAdmin; | ||
| } | ||
| } |
There was a problem hiding this comment.
This is a nice short-circuit
nintynick
left a comment
There was a problem hiding this comment.
Reviewed alongside the audit report and all the changes look great. Left specific review comments, mostly editorial.
| <p align="right">(<a href="#documentation-top">back to contents</a>)</p> | ||
| <p align="right">(<a href="#table-of-contents">back to contents</a>)</p> | ||
|
|
||
| ### ERC1155 Compatibility |
README.md
Outdated
| | --- | ---- | | ||
| | Signer on a multisig | Using the Hat's ERC1155 token as a condition for membership in an Orca Protocol Pod | | ||
| | Admin of the DAO's Github repo | Using the Hat's ERC1155 token as a condition for access via Lit Protocol | | ||
| | Signer on a multisig | Using the Hat's ERC1155--similar token as a condition for membership in an Orca Protocol Pod | |
There was a problem hiding this comment.
| | Signer on a multisig | Using the Hat's ERC1155--similar token as a condition for membership in an Orca Protocol Pod | | |
| | Signer on a multisig | Using the Hat's ERC1155-similar token as a condition for membership in an Orca Protocol Pod | |
|
|
||
| import { ERC1155 } from "lib/ERC1155/ERC1155.sol"; | ||
| // import "forge-std/Test.sol"; //remove after testing | ||
| // import { console2 } from "forge-std/Test.sol"; //remove after testing |
There was a problem hiding this comment.
yea, will remove before deploy
src/Hats.sol
Outdated
| /// @notice Hats are DAO-native, revocable, and programmable roles that are represented as non-transferable ERC-1155 tokens for composability | ||
| /// @dev This is a multitenant contract that can manage all hats for a given chain | ||
| /// @notice Hats are DAO-native, revocable, and programmable roles that are represented as non-transferable ERC-1155-similar tokens for composability | ||
| /// @dev This is a multitenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not comply full with the ERC1155 standard. |
There was a problem hiding this comment.
| /// @dev This is a multitenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not comply full with the ERC1155 standard. | |
| /// @dev This is a multitenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not fully comply with the ERC1155 standard. |
src/Hats.sol
Outdated
| /* | ||
| if function call succeeds with data of length == 32, then we know the contract exists | ||
| and has the getHatStatus function. | ||
| But we still can't assume that the return data is a boolean, so we need to check that manuallys |
There was a problem hiding this comment.
| But we still can't assume that the return data is a boolean, so we need to check that manuallys | |
| But we still can't assume that the return data is a boolean, so we need to check that manually |
| // false condition | ||
| if (uintReturndata == 0) { | ||
| active = false; | ||
| // true condition |
There was a problem hiding this comment.
| // true condition |
| if (uintReturndata == 0) { | ||
| active = false; | ||
| // true condition | ||
| } else if (uintReturndata == 1) { |
There was a problem hiding this comment.
| } else if (uintReturndata == 1) { | |
| } | |
| // true condition | |
| else if (uintReturndata == 1) { |
src/Hats.sol
Outdated
| /* | ||
| if function call succeeds with data of length == 32, then we know the contract exists | ||
| and has the getHatStatus function. | ||
| But we still can't assume that the return data is a boolean, so we need to check that manuallys |
There was a problem hiding this comment.
| But we still can't assume that the return data is a boolean, so we need to check that manuallys | |
| But we still can't assume that the return data is a boolean, so we need to check that manually |
| @@ -133,11 +162,11 @@ contract HatsIdUtilities is IHatsIdUtilities { | |||
| /// @return uint256 The hat id of the resulting admin | |||
| function getAdminAtLevel(uint256 _hatId, uint32 _level) public view returns (uint256) { | |||
| uint256 linkedTreeAdmin = linkedTreeAdmins[getTophatDomain(_hatId)]; | |||
| uint32 localTopHatLevel = getHatLevel(getLocalAdminAtLevel(_hatId, 0)); | ||
|
|
||
| if (localTopHatLevel <= _level) return getTreeAdminAtLevel(_hatId, _level - localTopHatLevel); | ||
| if (localTopHatLevel <= _level) return getLocalAdminAtLevel(_hatId, _level - localTopHatLevel); |
Uh oh!
There was an error while loading. Please reload this page.