Skip to content

Commit

Permalink
feat(HatsGatekeepers): add Hats gatekeeper contracts
Browse files Browse the repository at this point in the history
Adds two flavors of Hats Protocol-powered gatekeeper contracts:

- `*Single` gates registration to wearers of a single hat
- `*Multiple` gates registration to wearers of multiple hats.
  • Loading branch information
spengrah committed Feb 13, 2024
1 parent e38eb77 commit f1b0188
Show file tree
Hide file tree
Showing 4 changed files with 516 additions and 1 deletion.
127 changes: 127 additions & 0 deletions contracts/contracts/gatekeepers/HatsGatekeepers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { SignUpGatekeeper } from "./SignUpGatekeeper.sol";

interface IHats {
function isWearerOfHat(address account, uint256 hat) external view returns (bool);
}

// custom errors
error OnlyMACI();
error NotWearingCriterionHat();
error NotCriterionHat();
error AlreadyRegistered();

contract HatsGatekeeperSingle is SignUpGatekeeper, Ownable {
/// @notice The Hats Protocol contract address
IHats public immutable hats;

/// @notice The hat that users must wear to be eligible to register
uint256 public immutable criterionHat;

/// @notice the reference to the MACI contract
address public maci;

/// @notice Tracks registered users
mapping(address => bool) public registered;

/// @notice Deploy an instance of HatsGatekeeper
/// @param _hats The Hats Protocol contract
/// @param _criterionHat The required hat // could also accept multiple hats in an array
constructor(address _hats, uint256 _criterionHat) payable {
hats = IHats(_hats);
criterionHat = _criterionHat;
}

/*//////////////////////////////////////////////////////////////
OWNER FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @notice Allows to set the MACI contract
function setMaciInstance(address _maci) public override onlyOwner {
maci = _maci;
}

/*//////////////////////////////////////////////////////////////
GATEKEEPER FUNCTION
//////////////////////////////////////////////////////////////*/

/// @notice Registers the user
/// @param _user The address of the user
function register(address _user, bytes memory /*_data*/) public override {
// ensure that the caller is the MACI contract
if (maci != msg.sender) revert OnlyMACI();

// _user must not be already registered
if (registered[_user]) revert AlreadyRegistered();

registered[_user] = true;

// _user must be wearing the criterion hat
if (!hats.isWearerOfHat(_user, criterionHat)) revert NotWearingCriterionHat();
}
}

contract HatsGatekeeperMultiple is SignUpGatekeeper, Ownable {
/// @notice The Hats Protocol contract address
IHats public immutable hats;

/// @notice Tracks hats that users must wear to be eligible to register
mapping(uint256 => bool) public criterionHat;

/// @notice the reference to the MACI contract
address public maci;

/// @notice Tracks registered users
mapping(address => bool) public registered;

/// @notice Deploy an instance of HatsGatekeeper
/// @param _hats The Hats Protocol contract
/// @param _criterionHats Array of accepted criterion hats
constructor(address _hats, uint256[] memory _criterionHats) payable {
hats = IHats(_hats);

// add the criterion hats
for (uint256 i; i < _criterionHats.length; ++i) {
criterionHat[_criterionHats[i]] = true;
}
}

/*//////////////////////////////////////////////////////////////
OWNER FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @notice Allows to set the MACI contract
function setMaciInstance(address _maci) public override onlyOwner {
maci = _maci;
}

/*//////////////////////////////////////////////////////////////
GATEKEEPER FUNCTION
//////////////////////////////////////////////////////////////*/

/// @notice Registers the user
/// @param _user The address of the user
/// @param _data additional data
function register(address _user, bytes memory _data) public override {
// ensure that the caller is the MACI contract
if (maci != msg.sender) revert OnlyMACI();

// _user must not be already registered
if (registered[_user]) revert AlreadyRegistered();

// decode the _data as a hat
uint256 hat = abi.decode(_data, (uint256));

// the hat must be set as a criterion hat
if (!criterionHat[hat]) revert NotCriterionHat();

registered[_user] = true;

// _user must be wearing the criterion hat
if (!hats.isWearerOfHat(_user, hat)) revert NotWearingCriterionHat();
}
}
74 changes: 74 additions & 0 deletions contracts/contracts/mocks/MockHatsProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/// @title IHats
/// @notice The interface for the Hats Protocol contract
interface IHats {
function mintTopHat(address _target, string calldata _details, string calldata _imageURI) external returns (uint256);
function createHat(
uint256 _admin,
string calldata _details,
uint32 _maxSupply,
address _eligibility,
address _toggle,
bool _mutable,
string calldata _imageURI
) external returns (uint256);
function mintHat(uint256 _hatId, address _wearer) external returns (bool success);
}

/// @title MockHatsProtocol
/// @notice A mock contract to test the HatsGatekeeper
contract MockHatsProtocol {
IHats public immutable hats;

uint256 public lastTopHat;
uint256 public lastHat;

/// @notice Deploy an instance of MockHatsProtocol
/// @param _hats The Hats Protocol contract
constructor(address _hats) payable {
hats = IHats(_hats);
}

/// @notice Creates and mints a Hat that is its own admin, i.e. a "topHat"
/// @dev A topHat has no eligibility and no toggle
/// @param _target The address to which the newly created topHat is minted
/// @param _details A description of the Hat [optional]. Should not be larger than 7000 bytes
/// (enforced in changeHatDetails)
/// @param _imageURI The image uri for this top hat and the fallback for its
/// downstream hats [optional]. Should not be large than 7000 bytes
/// (enforced in changeHatImageURI)
function mintTopHat(address _target, string calldata _details, string calldata _imageURI) external {
lastTopHat = hats.mintTopHat(_target, _details, _imageURI);
}

/// @notice Creates a new hat. The msg.sender must wear the `_admin` hat.
/// @dev Initializes a new Hat struct, but does not mint any tokens.
/// @param _details A description of the Hat. Should not be larger than 7000 bytes (enforced in changeHatDetails)
/// @param _maxSupply The total instances of the Hat that can be worn at once
/// @param _admin The id of the Hat that will control who wears the newly created hat
/// @param _eligibility The address that can report on the Hat wearer's status
/// @param _toggle The address that can deactivate the Hat
/// @param _mutable Whether the hat's properties are changeable after creation
/// @param _imageURI The image uri for this hat and the fallback for its
/// downstream hats [optional]. Should not be larger than 7000 bytes (enforced in changeHatImageURI)
function createHat(
uint256 _admin,
string calldata _details,
uint32 _maxSupply,
address _eligibility,
address _toggle,
bool _mutable,
string calldata _imageURI
) external {
lastHat = hats.createHat(_admin, _details, _maxSupply, _eligibility, _toggle, _mutable, _imageURI);
}

/// @notice Mints a hat to the specified wearer
/// @param _hatId The id of the hat to mint
/// @param _wearer The address of the wearer
function mintHat(uint256 _hatId, address _wearer) external {
hats.mintHat(_hatId, _wearer);
}
}
3 changes: 2 additions & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"test:vkRegistry": "hardhat test ./tests/VkRegistry.test.ts",
"test:pollFactory": "hardhat test ./tests/PollFactory.test.ts",
"test:subsidy": "hardhat test ./tests/Subsidy.test.ts",
"test:eas_gatekeeper": "hardhat test ./tests/EASGatekeeper.test.ts",
"test:easGatekeeper": "hardhat test ./tests/EASGatekeeper.test.ts",
"test:hatsGatekeeper": "hardhat test ./tests/HatsGatekeeper.test.ts --network hardhat",
"deploy": "hardhat deploy-full",
"deploy-poll": "hardhat deploy-poll",
"verify": "hardhat verify-full",
Expand Down

0 comments on commit f1b0188

Please sign in to comment.