Skip to content

Commit

Permalink
Merge pull request #1 from Hats-Protocol/mutable-roles
Browse files Browse the repository at this point in the history
Convert module role hats to mutable
  • Loading branch information
spengrah authored May 13, 2024
2 parents dce7c6e + 6a61935 commit 23604b0
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 50 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ A [Hats Protocol](https://github.com/hats-protocol/hats-protocol) eligibility mo

## Overview and Usage

This module sets up a simple allowlist to determine eligibility for a hat. For a given account (i.e., potential hat wearer), the allowlist stores values for that account's eligibility and standing for the hat. The wearer(s) of the `OWNER_HAT` can add or remove accounts from the allowlist. The wearer(s) of the `ARBITRATOR_HAT` can set the standing of accounts.
This module sets up a simple allowlist to determine eligibility for a hat. For a given account (i.e., potential hat wearer), the allowlist stores values for that account's eligibility and standing for the hat. The wearer(s) of the `ownerHat` can add or remove accounts from the allowlist. The wearer(s) of the `arbitratorHat` can set the standing of accounts.

This module serves as both a "mechanistic" and "humanistic passthrough" eligibility module.
This module serves as both a "mechanistic" and "humanistic passthrough" eligibility module.

### Mechanistic Functionality
- Wearer(s) of the `OWNER_HAT` can simply add account(s) to the allowlist by calling `addAccount()` or `addAccounts()`.
- Wearer(s) of the `OWNER_HAT` can simply remove account(s) from the allowlist by calling `removeAccount()` or `removeAccounts()`.
- Wearer(s) of the `ARBITRATOR_HAT` can simply set the standing of account(s) by calling `setStandingForAccount()` or `setStandingForAccounts()`.

In each of these cases, Hats Protocol will *pull* eligibility and standing data from the module via `getWearerStatus()`. Hats Protocol will not emit an event with any of these eligibility and resulting wearer changes, so front ends pointing only at Hats Protocol events (or the [subgraph](https://github.com/hats-protocol/subgraph)) will not automatically reclect these changes.
- Wearer(s) of the `ownerHat` can simply add account(s) to the allowlist by calling `addAccount()` or `addAccounts()`.
- Wearer(s) of the `ownerHat` can simply remove account(s) from the allowlist by calling `removeAccount()` or `removeAccounts()`.
- Wearer(s) of the `arbitratorHat` can simply set the standing of account(s) by calling `setStandingForAccount()` or `setStandingForAccounts()`.

### Humanistic Functionality
- Wearer(s) of the `OWNER_HAT` can manually revoke an account's hat by calling `removeAccountAndBurnHat()`.
- Wearer(s) of the `ARBITRATOR_HAT` can manually put an account in bad standing and burn their hat `setStandingForAccountAndBurnHat()`.
In each of these cases, Hats Protocol will *pull* eligibility and standing data from the module via `getWearerStatus()`. Hats Protocol will not emit an event with any of these eligibility and resulting wearer changes, so front ends pointing only at Hats Protocol events (or the [subgraph](https://github.com/hats-protocol/subgraph)) will not automatically reflect these changes.

In these cases, the module *pushes* eligibility and standing data to Hats Protocol, causing Hats Protocol to emit event(s) reflecting the eligibility and resulting wearer changes. Front ends pointing at Hats Protocol events (or the subgaph) *will* automatically reflect these changes.
### Humanistic Functionality

- Wearer(s) of the `ownerHat` can manually revoke an account's hat by calling `removeAccountAndBurnHat()`.
- Wearer(s) of the `arbitratorHat` can manually put an account in bad standing and burn their hat `setStandingForAccountAndBurnHat()`.

In these cases, the module *pushes* eligibility and standing data to Hats Protocol, causing Hats Protocol to emit event(s) reflecting the eligibility and resulting wearer changes. Front ends pointing at Hats Protocol events (or the subgraph) *will* automatically reflect these changes.

## Development

Expand Down
4 changes: 2 additions & 2 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AllowlistEligibility } from "../src/AllowlistEligibility.sol";

contract Deploy is Script {
AllowlistEligibility public implementation;
bytes32 public SALT = bytes32(abi.encode("change this to the value of your choice"));
bytes32 public SALT = bytes32(abi.encode(0x4a75));

// default values
bool internal _verbose = true;
Expand Down Expand Up @@ -42,7 +42,7 @@ contract Deploy is Script {
* never differs regardless of where its being compiled
* 2. The provided salt, `SALT`
*/
implementation = new AllowlistEligibility{ salt: SALT}(_version /* insert constructor args here */);
implementation = new AllowlistEligibility{ salt: SALT }(_version /* insert constructor args here */ );

vm.stopBroadcast();

Expand Down
129 changes: 98 additions & 31 deletions src/AllowlistEligibility.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// import { console2 } from "forge-std/Test.sol"; // remove before deploy
// import { console2 } from "forge-std/Test.sol"; // comment out before deploy
import { HatsEligibilityModule, HatsModule, IHatsEligibility } from "hats-module/HatsEligibilityModule.sol";

/*//////////////////////////////////////////////////////////////
CUSTOM ERRORS
//////////////////////////////////////////////////////////////*/

/// @dev Thrown when the caller does not wear the `OWNER_HAT`
/// @dev Thrown when the caller does not wear the `ownerHat`
error AllowlistEligibility_NotOwner();
/// @dev Thrown when the caller does not wear the `ARBITRATOR_HAT`
/// @dev Thrown when the caller does not wear the `arbitratorHat`
error AllowlistEligibility_NotArbitrator();
/// @dev Thrown when array args are not the same length
error AllowlistEligibility_ArrayLengthMismatch();
/// @dev Thrown if attempting to burn a hat that an account is not wearing
error AllowlistEligibility_NotWearer();
/// @dev Thrown if the hat is not mutable
error AllowlistEligibility_HatNotMutable();

/**
* @title AllowlistEligibility
Expand Down Expand Up @@ -43,6 +45,10 @@ contract AllowlistEligibility is HatsEligibilityModule {
event AccountStandingChanged(address account, bool standing);
/// @notice Emitted when multiple accounts' standing are changed
event AccountsStandingChanged(address[] accounts, bool[] standing);
/// @notice Emitted when a new ownerHat is set
event OwnerHatSet(uint256 newOwnerHat);
/// @notice Emitted when a new arbitratorHat is set
event ArbitratorHatSet(uint256 newArbitratorHat);

/*//////////////////////////////////////////////////////////////
DATA MODELS
Expand Down Expand Up @@ -80,26 +86,20 @@ contract AllowlistEligibility is HatsEligibilityModule {
* 0 | IMPLEMENTATION | address | 20 | HatsModule |
* 20 | HATS | address | 20 | HatsModule |
* 40 | hatId | uint256 | 32 | HatsModule |
* 72 | OWNER_HAT | uint256 | 32 | this |
* 104 | ARBITRATOR_HAT | uint256 | 32 | this |
* ----------------------------------------------------------------------+
*/

/*//////////////////////////////////////////////////////////////
MUTABLE STATE
//////////////////////////////////////////////////////////////*/

/// @notice The hat ID for the owner hat. The wearer(s) of this hat are authorized to add and remove accounts from the
/// allowlist
function OWNER_HAT() public pure returns (uint256) {
return _getArgUint256(72);
}
uint256 public ownerHat;

/// @notice The hat ID for the arbitrator hat. The wearer(s) of this hat are authorized to set the standing for
/// accounts.
function ARBITRATOR_HAT() public pure returns (uint256) {
return _getArgUint256(104);
}

/*//////////////////////////////////////////////////////////////
MUTABLE STATE
//////////////////////////////////////////////////////////////*/
uint256 public arbitratorHat;

/**
* @notice The eligibility data for each account
Expand All @@ -122,11 +122,29 @@ contract AllowlistEligibility is HatsEligibilityModule {

/// @inheritdoc HatsModule
function _setUp(bytes calldata _initData) internal override {
// if there are no initial accounts to add, only initialize the clone instance
if (_initData.length == 0) return;
uint256 _ownerHat;
uint256 _arbitratorHat;

// if there are no initial accounts to add, only set the owner and arbitrator hats
if (_initData.length < 65) {
// decode init data to look for hats
(_ownerHat, _arbitratorHat) = abi.decode(_initData, (uint256, uint256));

// set the owner and arbitrator hats
_setOwnerHat(_ownerHat);
_setArbitratorHat(_arbitratorHat);

return;
}

// otherwise, decode init data to look for hats and initial accounts to add
address[] memory _accounts;
(_ownerHat, _arbitratorHat, _accounts) = abi.decode(_initData, (uint256, uint256, address[]));

// set the owner and arbitrator hats
_setOwnerHat(_ownerHat);
_setArbitratorHat(_arbitratorHat);

// otherwise, decode init data to look for initial accounts to add
address[] memory _accounts = abi.decode(_initData, (address[]));
// add initial accounts to allowlist
_addAccountsMemory(_accounts);
}
Expand Down Expand Up @@ -158,7 +176,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Add an account to the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* @dev Only callable by a wearer of the ownerHat
* Does not revert if account is already added; overwrites existing eligibility data for the account
* @param _account The account to add
*/
Expand All @@ -170,7 +188,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Add multiple accounts to the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* @dev Only callable by a wearer of the ownerHat
* Does not revert if an account is already added; overwrites existing eligibility data for the account
* @param _accounts The array of accounts to add
*/
Expand All @@ -187,7 +205,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Remove an account from the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* @dev Only callable by a wearer of the ownerHat
* Does not revert if account is not yet added
* Revokes the account's hat if they are wearing it, but no burn event will be emitted
* @param _account The account to remove
Expand All @@ -200,7 +218,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Remove an account from the allowlist and revoke their hat
* @dev Only callable by a wearer of the OWNER_HAT
* @dev Only callable by a wearer of the ownerHat
* Will revert if the account is not wearing the hat
* Reverts if the account is not wearing the hat, but other does not revert if account is not yet added
* @param _account The account to remove
Expand All @@ -227,7 +245,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Remove multiple accounts from the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* @dev Only callable by a wearer of the ownerHat
* Does not revert if an account is not yet added
* @param _accounts The array of accounts to remove
*/
Expand All @@ -244,7 +262,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Set the standing for an account
* @dev Only callable by a wearer of the ARBITRATOR_HAT
* @dev Only callable by a wearer of the arbitratorHat
* Does not revert if an account is not yet added
* @param _account The account to set standing for
* @param _standing The standing to set
Expand All @@ -257,7 +275,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Puts an account in bad standing and burns their hat
* @dev Only callable by a wearer of the ARBITRATOR_HAT
* @dev Only callable by a wearer of the arbitratorHat
* Reverts if the account is not wearing the hat, but otherwise does not revert if an account is not yet added
* @param _account The account to set standing for
*/
Expand All @@ -281,7 +299,7 @@ contract AllowlistEligibility is HatsEligibilityModule {

/**
* @notice Set the standing for multiple accounts
* @dev Only callable by a wearer of the ARBITRATOR_HAT
* @dev Only callable by a wearer of the arbitratorHat
* Does not revert if an account is not yet added
* @param _accounts The array of accounts to set standing for
* @param _standing The array of standings to set, indexed to the accounts array
Expand All @@ -300,6 +318,28 @@ contract AllowlistEligibility is HatsEligibilityModule {
emit AccountsStandingChanged(_accounts, _standing);
}

/**
* @notice Set a new owner hat
* @dev Only callable by a wearer of the current ownerHat, and only if the target hat is mutable
* @param _newOwnerHat The new owner hat
*/
function setOwnerHat(uint256 _newOwnerHat) public onlyOwner hatIsMutable {
ownerHat = _newOwnerHat;

emit OwnerHatSet(_newOwnerHat);
}

/**
* @notice Set a new arbitrator hat
* @dev Only callable by a wearer of the current ownerHat, and only if the target hat is mutable
* @param _newArbitratorHat The new arbitrator hat
*/
function setArbitratorHat(uint256 _newArbitratorHat) public onlyOwner hatIsMutable {
arbitratorHat = _newArbitratorHat;

emit ArbitratorHatSet(_newArbitratorHat);
}

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand All @@ -320,19 +360,46 @@ contract AllowlistEligibility is HatsEligibilityModule {
emit AccountsAdded(_accounts);
}

/**
* @dev Set a new owner hat
* @param _newOwnerHat The new owner hat
*/
function _setOwnerHat(uint256 _newOwnerHat) internal {
ownerHat = _newOwnerHat;

emit OwnerHatSet(_newOwnerHat);
}

/**
* @dev Set a new arbitrator hat
* @param _newArbitratorHat The new arbitrator hat
*/
function _setArbitratorHat(uint256 _newArbitratorHat) internal {
arbitratorHat = _newArbitratorHat;

emit ArbitratorHatSet(_newArbitratorHat);
}

/*//////////////////////////////////////////////////////////////
MODIFERS
//////////////////////////////////////////////////////////////*/

/// @notice Reverts if the caller is not wearing the OWNER_HAT.
/// @notice Reverts if the caller is not wearing the ownerHat.
modifier onlyOwner() {
if (!HATS().isWearerOfHat(msg.sender, OWNER_HAT())) revert AllowlistEligibility_NotOwner();
if (!HATS().isWearerOfHat(msg.sender, ownerHat)) revert AllowlistEligibility_NotOwner();
_;
}

/// @notice Reverts if the caller is not wearing the ARBITRATOR_HAT.
/// @notice Reverts if the caller is not wearing the arbitratorHat.
modifier onlyArbitrator() {
if (!HATS().isWearerOfHat(msg.sender, ARBITRATOR_HAT())) revert AllowlistEligibility_NotArbitrator();
if (!HATS().isWearerOfHat(msg.sender, arbitratorHat)) revert AllowlistEligibility_NotArbitrator();
_;
}

/// @notice Reverts if the hatid is not mutable
modifier hatIsMutable() {
(,,,,,,, bool isMutable,) = HATS().viewHat(hatId());
if (!isMutable) revert AllowlistEligibility_HatNotMutable();
_;
}
}
Loading

0 comments on commit 23604b0

Please sign in to comment.