Skip to content

Commit

Permalink
initial logic compiling
Browse files Browse the repository at this point in the history
  • Loading branch information
spengrah committed Aug 24, 2023
1 parent 8fa38c9 commit 5998168
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 93 deletions.
8 changes: 4 additions & 4 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
pragma solidity ^0.8.18;

import { Script, console2 } from "forge-std/Script.sol";
import { Module } from "../src/Module.sol";
import { AllowlistEligibility } from "../src/AllowlistEligibility.sol";

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

// default values
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 Module{ salt: SALT}(_version /* insert constructor args here */);
implementation = new AllowlistEligibility{ salt: SALT}(_version /* insert constructor args here */);

vm.stopBroadcast();

Expand All @@ -60,7 +60,7 @@ contract DeployPrecompiled is Deploy {
bytes memory args = abi.encode( /* insert constructor args here */ );

/// @dev Load and deploy pre-compiled ir-optimized bytecode.
implementation = Module(deployCode("optimized-out/Module.sol/Module.json", args));
implementation = AllowlistEligibility(deployCode("optimized-out/Module.sol/Module.json", args));

vm.stopBroadcast();

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

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

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

/// @dev Thrown when the caller does not wear the `OWNER_HAT`
error AllowlistEligibility_NotOwner();
/// @dev Thrown when the caller does not wear the `ARBITRATOR_HAT`
error AllowlistEligibility_NotArbitrator();
/// @dev Thrown when array args are not the same length
error AllowlistEligibility_ArrayLengthMismatch();

/// @title AllowlistEligibility
/// @author spengrah
/// @author Haberdasher Labs
/// @notice A Hats Protocol eligibility that allows the owner to add and remove accounts from an allowlist
/// @dev This contract inherits from HatsEligibilityModule and is designed to deployed as a clone via HatsModuleFactory
contract AllowlistEligibility is HatsEligibilityModule {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/

/// @notice Emitted when an account is added to the allowlist
event AccountAdded(address account);
/// @notice Emitted when multiple accounts are added to the allowlist
event AccountsAdded(address[] accounts);
/// @notice Emitted when an account is removed from the allowlist
event AccountRemoved(address account);
/// @notice Emitted when multiple accounts are removed from the allowlist
event AccountsRemoved(address[] accounts);
/// @notice Emitted when an account's standing is changed
event AccountStandingChanged(address account, bool standing);
/// @notice Emitted when multiple accounts' standing are changed
event AccountsStandingChanged(address[] accounts, bool[] standing);

/*//////////////////////////////////////////////////////////////
DATA MODELS
//////////////////////////////////////////////////////////////*/

struct EligibilityData {
/// @notice Whether the wearer is eligible for the hat
/// @dev Defaults to false, ie not eligible
bool eligible;
/// @notice Whether the wearer is in bad standing
/// @dev Defaults to false, ie good standing
bool badStanding;
}

/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/

/**
* This contract is a clone with immutable args, which means that it is deployed with a set of
* immutable storage variables (ie constants). Accessing these constants is cheaper than accessing
* regular storage variables (such as those set on initialization of a typical EIP-1167 clone),
* but requires a slightly different approach since they are read from calldata instead of storage.
*
* Below is a table of constants and their location.
*
* For more, see here: https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args
*
* ----------------------------------------------------------------------+
* CLONE IMMUTABLE "STORAGE" |
* ----------------------------------------------------------------------|
* Offset | Constant | Type | Length | Source |
* ----------------------------------------------------------------------|
* 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 |
* ----------------------------------------------------------------------+
*/

function OWNER_HAT() public pure returns (uint256) {
return _getArgUint256(72);
}

function ARBITRATOR_HAT() public pure returns (uint256) {
return _getArgUint256(104);
}

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

/**
* @notice The eligibility data for each account
* @custom:param account The account to get eligibility data for
* @custom:return eligibility The eligibility data for the account
*/
mapping(address account => EligibilityData eligibility) public allowlist;

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/

/// @notice Deploy the implementation contract and set its version
/// @dev This is only used to deploy the implementation contract, and should not be used to deploy clones
constructor(string memory _version) HatsModule(_version) { }

/*//////////////////////////////////////////////////////////////
INITIALIZER
//////////////////////////////////////////////////////////////*/

/// @inheritdoc HatsModule
function setUp(bytes calldata _initData) public override initializer {
// decode init data
address[] memory _accounts = abi.decode(_initData, (address[]));
// add initial accounts to allowlist
_addAccountsMemory(_accounts);
}

/*//////////////////////////////////////////////////////////////
HATS ELIGIBILITY FUNCTION
//////////////////////////////////////////////////////////////*/

/// @inheritdoc IHatsEligibility
function getWearerStatus(address _wearer, uint256 /* _hatId */ )
public
view
override
returns (bool _eligible, bool _standing)
{
// load a pointer to the eligibility data in storage
EligibilityData storage eligibility = allowlist[_wearer];

// wearer is always ineligible if in bad standing
if (eligibility.badStanding) return (false, false);

_standing = true;
_eligible = eligibility.eligible;
}

/*//////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @notice Add an account to the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* Note: overwrites existing eligibility data for the account
* @param _account The account to add
*/
function addAccount(address _account) public onlyOwner {
allowlist[_account].eligible = true;

emit AccountAdded(_account);
}

/**
* @notice Add multiple accounts to the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* Note: overwrites existing eligibility data for the accounts
* @param _accounts The array of accounts to add
*/
function addAccounts(address[] calldata _accounts) public onlyOwner {
for (uint256 i; i < _accounts.length;) {
allowlist[_accounts[i]].eligible = true;
unchecked {
++i;
}
}

emit AccountsAdded(_accounts);
}

/**
* @notice Remove an account from the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* Note: overwrites existing eligibility data for the account
* @param _account The account to remove
*/
function removeAccount(address _account) public onlyOwner {
allowlist[_account].eligible = false;

emit AccountRemoved(_account);
}

/**
* @notice Remove multiple accounts from the allowlist
* @dev Only callable by a wearer of the OWNER_HAT
* Note: overwrites existing eligibility data for the accounts
* @param _accounts The array of accounts to remove
*/
function removeAccounts(address[] calldata _accounts) public onlyOwner {
for (uint256 i; i < _accounts.length;) {
allowlist[_accounts[i]].eligible = false;
unchecked {
++i;
}
}

emit AccountsRemoved(_accounts);
}

/**
* @notice Set the standing for an account
* @dev Only callable by a wearer of the ARBITRATOR_HAT
* Note: overwrites existing standing data for the account
* @param _account The account to set standing for
* @param _standing The standing to set
*/
function setStandingForAccount(address _account, bool _standing) public onlyArbitrator {
allowlist[_account].badStanding = !_standing;

emit AccountStandingChanged(_account, _standing);
}

/**
* @notice Set the standing for multiple accounts
* @dev Only callable by a wearer of the ARBITRATOR_HAT
* Note: overwrites existing standing data for the accounts
* @param _accounts The array of accounts to set standing for
* @param _standing The array of standings to set, indexed to the accounts array
*/
function setStandingForAccounts(address[] calldata _accounts, bool[] calldata _standing) public onlyArbitrator {
// arrays must be the same length
if (_accounts.length != _standing.length) revert AllowlistEligibility_ArrayLengthMismatch();

for (uint256 i; i < _accounts.length;) {
allowlist[_accounts[i]].badStanding = !_standing[i];
unchecked {
++i;
}
}

emit AccountsStandingChanged(_accounts, _standing);
}

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @dev Add multiple accounts to the allowlist, using memory instead of calldata for compatibility with {setUp}
* Note: overwrites existing eligibility data for the accounts
* @param _accounts The array of accounts to add
*/
function _addAccountsMemory(address[] memory _accounts) internal {
for (uint256 i; i < _accounts.length;) {
allowlist[_accounts[i]].eligible = true;
unchecked {
++i;
}
}

emit AccountsAdded(_accounts);
}

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

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

/// @notice Reverts if the caller is not wearing the ARBITRATOR_HAT.
modifier onlyArbitrator() {
if (!HATS().isWearerOfHat(msg.sender, ARBITRATOR_HAT())) revert AllowlistEligibility_NotArbitrator();
_;
}
}
82 changes: 0 additions & 82 deletions src/Module.sol

This file was deleted.

Loading

0 comments on commit 5998168

Please sign in to comment.