Commit e4f5535d authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6381 from ethereum-optimism/refcell/op-nft-styling

fix(ctb): Port op-nft periphery contracts to triple slash natspec styling
parents 00e2137a 80280e1e
...@@ -3,39 +3,29 @@ pragma solidity 0.8.15; ...@@ -3,39 +3,29 @@ pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol"; import { Semver } from "../../universal/Semver.sol";
/** /// @title AttestationStation
* @title AttestationStation /// @author Optimism Collective
* @author Optimism Collective /// @author Gitcoin
* @author Gitcoin /// @notice Where attestations live.
* @notice Where attestations live.
*/
contract AttestationStation is Semver { contract AttestationStation is Semver {
/** /// @notice Struct representing data that is being attested.
* @notice Struct representing data that is being attested. /// @custom:field about Address for which the attestation is about.
* /// @custom:field key A bytes32 key for the attestation.
* @custom:field about Address for which the attestation is about. /// @custom:field val The attestation as arbitrary bytes.
* @custom:field key A bytes32 key for the attestation.
* @custom:field val The attestation as arbitrary bytes.
*/
struct AttestationData { struct AttestationData {
address about; address about;
bytes32 key; bytes32 key;
bytes val; bytes val;
} }
/** /// @notice Maps addresses to attestations. Creator => About => Key => Value.
* @notice Maps addresses to attestations. Creator => About => Key => Value.
*/
mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations; mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;
/** /// @notice Emitted when Attestation is created.
* @notice Emitted when Attestation is created. /// @param creator Address that made the attestation.
* /// @param about Address attestation is about.
* @param creator Address that made the attestation. /// @param key Key of the attestation.
* @param about Address attestation is about. /// @param val Value of the attestation.
* @param key Key of the attestation.
* @param val Value of the attestation.
*/
event AttestationCreated( event AttestationCreated(
address indexed creator, address indexed creator,
address indexed about, address indexed about,
...@@ -43,18 +33,13 @@ contract AttestationStation is Semver { ...@@ -43,18 +33,13 @@ contract AttestationStation is Semver {
bytes val bytes val
); );
/** /// @custom:semver 1.1.0
* @custom:semver 1.1.0
*/
constructor() Semver(1, 1, 0) {} constructor() Semver(1, 1, 0) {}
/** /// @notice Allows anyone to create an attestation.
* @notice Allows anyone to create an attestation. /// @param _about Address that the attestation is about.
* /// @param _key A key used to namespace the attestation.
* @param _about Address that the attestation is about. /// @param _val An arbitrary value stored as part of the attestation.
* @param _key A key used to namespace the attestation.
* @param _val An arbitrary value stored as part of the attestation.
*/
function attest( function attest(
address _about, address _about,
bytes32 _key, bytes32 _key,
...@@ -65,11 +50,8 @@ contract AttestationStation is Semver { ...@@ -65,11 +50,8 @@ contract AttestationStation is Semver {
emit AttestationCreated(msg.sender, _about, _key, _val); emit AttestationCreated(msg.sender, _about, _key, _val);
} }
/** /// @notice Allows anyone to create attestations.
* @notice Allows anyone to create attestations. /// @param _attestations An array of AttestationData structs.
*
* @param _attestations An array of AttestationData structs.
*/
function attest(AttestationData[] calldata _attestations) external { function attest(AttestationData[] calldata _attestations) external {
uint256 length = _attestations.length; uint256 length = _attestations.length;
for (uint256 i = 0; i < length; ) { for (uint256 i = 0; i < length; ) {
......
...@@ -9,41 +9,29 @@ import { AttestationStation } from "./AttestationStation.sol"; ...@@ -9,41 +9,29 @@ import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol"; import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/** /// @author Optimism Collective
* @author Optimism Collective /// @author Gitcoin
* @author Gitcoin /// @title Optimist
* @title Optimist /// @notice A Soul Bound Token for real humans only(tm).
* @notice A Soul Bound Token for real humans only(tm).
*/
contract Optimist is ERC721BurnableUpgradeable, Semver { contract Optimist is ERC721BurnableUpgradeable, Semver {
/** /// @notice Attestation key used by the attestor to attest the baseURI.
* @notice Attestation key used by the attestor to attest the baseURI.
*/
bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri"); bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");
/** /// @notice Attestor who attests to baseURI.
* @notice Attestor who attests to baseURI.
*/
address public immutable BASE_URI_ATTESTOR; address public immutable BASE_URI_ATTESTOR;
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; AttestationStation public immutable ATTESTATION_STATION;
/** /// @notice Address of the OptimistAllowlist contract.
* @notice Address of the OptimistAllowlist contract.
*/
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST; OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;
/** /// @custom:semver 2.0.0
* @custom:semver 2.0.0 /// @param _name Token name.
* @param _name Token name. /// @param _symbol Token symbol.
* @param _symbol Token symbol. /// @param _baseURIAttestor Address of the baseURI attestor.
* @param _baseURIAttestor Address of the baseURI attestor. /// @param _attestationStation Address of the AttestationStation contract.
* @param _attestationStation Address of the AttestationStation contract. /// @param _optimistAllowlist Address of the OptimistAllowlist contract
* @param _optimistAllowlist Address of the OptimistAllowlist contract
*/
constructor( constructor(
string memory _name, string memory _name,
string memory _symbol, string memory _symbol,
...@@ -57,109 +45,81 @@ contract Optimist is ERC721BurnableUpgradeable, Semver { ...@@ -57,109 +45,81 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
initialize(_name, _symbol); initialize(_name, _symbol);
} }
/** /// @notice Initializes the Optimist contract.
* @notice Initializes the Optimist contract. /// @param _name Token name.
* /// @param _symbol Token symbol.
* @param _name Token name.
* @param _symbol Token symbol.
*/
function initialize(string memory _name, string memory _symbol) public initializer { function initialize(string memory _name, string memory _symbol) public initializer {
__ERC721_init(_name, _symbol); __ERC721_init(_name, _symbol);
__ERC721Burnable_init(); __ERC721Burnable_init();
} }
/** /// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation
* @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation /// of the recipient's address. Recipients must be permitted to mint, eventually anyone
* of the recipient's address. Recipients must be permitted to mint, eventually anyone /// will be able to mint. One token per address.
* will be able to mint. One token per address. /// @param _recipient Address of the token recipient.
*
* @param _recipient Address of the token recipient.
*/
function mint(address _recipient) public { function mint(address _recipient) public {
require(isOnAllowList(_recipient), "Optimist: address is not on allowList"); require(isOnAllowList(_recipient), "Optimist: address is not on allowList");
_safeMint(_recipient, tokenIdOfAddress(_recipient)); _safeMint(_recipient, tokenIdOfAddress(_recipient));
} }
/** /// @notice Returns the baseURI for all tokens.
* @notice Returns the baseURI for all tokens. /// @return uri_ BaseURI for all tokens.
* function baseURI() public view returns (string memory uri_) {
* @return BaseURI for all tokens. uri_ = string(
*/ abi.encodePacked(
function baseURI() public view returns (string memory) { ATTESTATION_STATION.attestations(
return BASE_URI_ATTESTOR,
string( address(this),
abi.encodePacked( bytes32("optimist.base-uri")
ATTESTATION_STATION.attestations(
BASE_URI_ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
) )
); )
);
} }
/** /// @notice Returns the token URI for a given token by ID
* @notice Returns the token URI for a given token by ID /// @param _tokenId Token ID to query.
* /// @return uri_ Token URI for the given token by ID.
* @param _tokenId Token ID to query. function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) {
uri_ = string(
* @return Token URI for the given token by ID. abi.encodePacked(
*/ baseURI(),
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) { "/",
return // Properly format the token ID as a 20 byte hex string (address).
string( Strings.toHexString(_tokenId, 20),
abi.encodePacked( ".json"
baseURI(), )
"/", );
// Properly format the token ID as a 20 byte hex string (address).
Strings.toHexString(_tokenId, 20),
".json"
)
);
} }
/** /// @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
* @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint /// the Optimist NFT. Since the Optimist NFT will also be used as part of the
* the Optimist NFT. Since the Optimist NFT will also be used as part of the /// Citizens House, mints are currently restricted. Eventually anyone will be able
* Citizens House, mints are currently restricted. Eventually anyone will be able /// to mint.
* to mint. /// @return allowed_ Whether or not the address is allowed to mint yet.
* function isOnAllowList(address _recipient) public view returns (bool allowed_) {
* @return Whether or not the address is allowed to mint yet. allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
*/
function isOnAllowList(address _recipient) public view returns (bool) {
return OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
} }
/** /// @notice Returns the token ID for the token owned by a given address. This is the uint256
* @notice Returns the token ID for the token owned by a given address. This is the uint256 /// representation of the given address.
* representation of the given address. /// @return Token ID for the token owned by the given address.
*
* @return Token ID for the token owned by the given address.
*/
function tokenIdOfAddress(address _owner) public pure returns (uint256) { function tokenIdOfAddress(address _owner) public pure returns (uint256) {
return uint256(uint160(_owner)); return uint256(uint160(_owner));
} }
/** /// @notice Disabled for the Optimist NFT (Soul Bound Token).
* @notice Disabled for the Optimist NFT (Soul Bound Token).
*/
function approve(address, uint256) public pure override { function approve(address, uint256) public pure override {
revert("Optimist: soul bound token"); revert("Optimist: soul bound token");
} }
/** /// @notice Disabled for the Optimist NFT (Soul Bound Token).
* @notice Disabled for the Optimist NFT (Soul Bound Token).
*/
function setApprovalForAll(address, bool) public virtual override { function setApprovalForAll(address, bool) public virtual override {
revert("Optimist: soul bound token"); revert("Optimist: soul bound token");
} }
/** /// @notice Prevents transfers of the Optimist NFT (Soul Bound Token).
* @notice Prevents transfers of the Optimist NFT (Soul Bound Token). /// @param _from Address of the token sender.
* /// @param _to Address of the token recipient.
* @param _from Address of the token sender.
* @param _to Address of the token recipient.
*/
function _beforeTokenTransfer( function _beforeTokenTransfer(
address _from, address _from,
address _to, address _to,
......
...@@ -5,54 +5,37 @@ import { Semver } from "../../universal/Semver.sol"; ...@@ -5,54 +5,37 @@ import { Semver } from "../../universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol"; import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "./libraries/OptimistConstants.sol"; import { OptimistConstants } from "./libraries/OptimistConstants.sol";
/** /// @title OptimistAllowlist
* @title OptimistAllowlist /// @notice Source of truth for whether an address is able to mint an Optimist NFT.
* @notice Source of truth for whether an address is able to mint an Optimist NFT. /// isAllowedToMint function checks various signals to return boolean value
isAllowedToMint function checks various signals to return boolean value for whether an /// for whether an address is eligible or not.
address is eligible or not.
*/
contract OptimistAllowlist is Semver { contract OptimistAllowlist is Semver {
/** /// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
* @notice Attestation key used by the AllowlistAttestor to manually add addresses to the /// allowlist.
* allowlist.
*/
bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint"); bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint");
/** /// @notice Attestation key used by Coinbase to issue attestations for Quest participants.
* @notice Attestation key used by Coinbase to issue attestations for Quest participants.
*/
bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY = bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY =
bytes32("coinbase.quest-eligible"); bytes32("coinbase.quest-eligible");
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; AttestationStation public immutable ATTESTATION_STATION;
/** /// @notice Attestor that issues 'optimist.can-mint' attestations.
* @notice Attestor that issues 'optimist.can-mint' attestations.
*/
address public immutable ALLOWLIST_ATTESTOR; address public immutable ALLOWLIST_ATTESTOR;
/** /// @notice Attestor that issues 'coinbase.quest-eligible' attestations.
* @notice Attestor that issues 'coinbase.quest-eligible' attestations.
*/
address public immutable COINBASE_QUEST_ATTESTOR; address public immutable COINBASE_QUEST_ATTESTOR;
/** /// @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite'
* @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite' /// attestations.
* attestations.
*/
address public immutable OPTIMIST_INVITER; address public immutable OPTIMIST_INVITER;
/** /// @custom:semver 1.0.0
* @custom:semver 1.0.0 /// @param _attestationStation Address of the AttestationStation contract.
* /// @param _allowlistAttestor Address of the allowlist attestor.
* @param _attestationStation Address of the AttestationStation contract. /// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _allowlistAttestor Address of the allowlist attestor. /// @param _optimistInviter Address of the OptimistInviter contract.
* @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _optimistInviter Address of the OptimistInviter contract.
*/
constructor( constructor(
AttestationStation _attestationStation, AttestationStation _attestationStation,
address _allowlistAttestor, address _allowlistAttestor,
...@@ -65,95 +48,83 @@ contract OptimistAllowlist is Semver { ...@@ -65,95 +48,83 @@ contract OptimistAllowlist is Semver {
OPTIMIST_INVITER = _optimistInviter; OPTIMIST_INVITER = _optimistInviter;
} }
/** /// @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
* @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the /// Optimist NFT will also be used as part of the Citizens House, mints are currently
* Optimist NFT will also be used as part of the Citizens House, mints are currently /// restricted. Eventually anyone will be able to mint.
* restricted. Eventually anyone will be able to mint. /// Currently, address is allowed to mint if it satisfies any of the following:
* /// 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor.
* Currently, address is allowed to mint if it satisfies any of the following: /// 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor
* 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor. /// 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter
* 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor /// contract.
* 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter /// @param _claimer Address to check.
* contract. /// @return allowed_ Whether or not the address is allowed to mint yet.
* function isAllowedToMint(address _claimer) public view returns (bool allowed_) {
* @param _claimer Address to check. allowed_ =
*
* @return Whether or not the address is allowed to mint yet.
*/
function isAllowedToMint(address _claimer) public view returns (bool) {
return
_hasAttestationFromAllowlistAttestor(_claimer) || _hasAttestationFromAllowlistAttestor(_claimer) ||
_hasAttestationFromCoinbaseQuestAttestor(_claimer) || _hasAttestationFromCoinbaseQuestAttestor(_claimer) ||
_hasAttestationFromOptimistInviter(_claimer); _hasAttestationFromOptimistInviter(_claimer);
} }
/** /// @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the
* @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the /// allowlist attestor.
* allowlist attestor. /// @param _claimer Address to check.
* /// @return valid_ Whether or not the address has a valid attestation.
* @param _claimer Address to check. function _hasAttestationFromAllowlistAttestor(address _claimer)
* internal
* @return Whether or not the address has a valid attestation. view
*/ returns (bool valid_)
function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool) { {
// Expected attestation value is bytes32("true") // Expected attestation value is bytes32("true")
return valid_ = _hasValidAttestation(
_hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY); ALLOWLIST_ATTESTOR,
_claimer,
OPTIMIST_CAN_MINT_ATTESTATION_KEY
);
} }
/** /// @notice Checks whether an address has a valid attestation from the Coinbase attestor.
* @notice Checks whether an address has a valid attestation from the Coinbase attestor. /// @param _claimer Address to check.
* /// @return valid_ Whether or not the address has a valid attestation.
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromCoinbaseQuestAttestor(address _claimer) function _hasAttestationFromCoinbaseQuestAttestor(address _claimer)
internal internal
view view
returns (bool) returns (bool valid_)
{ {
// Expected attestation value is bytes32("true") // Expected attestation value is bytes32("true")
return valid_ = _hasValidAttestation(
_hasValidAttestation( COINBASE_QUEST_ATTESTOR,
COINBASE_QUEST_ATTESTOR, _claimer,
_claimer, COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY
COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY );
);
} }
/** /// @notice Checks whether an address has a valid attestation from the OptimistInviter contract.
* @notice Checks whether an address has a valid attestation from the OptimistInviter contract. /// @param _claimer Address to check.
* /// @return valid_ Whether or not the address has a valid attestation.
* @param _claimer Address to check. function _hasAttestationFromOptimistInviter(address _claimer)
* internal
* @return Whether or not the address has a valid attestation. view
*/ returns (bool valid_)
function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool) { {
// Expected attestation value is the inviter's address // Expected attestation value is the inviter's address
return valid_ = _hasValidAttestation(
_hasValidAttestation( OPTIMIST_INVITER,
OPTIMIST_INVITER, _claimer,
_claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY );
);
} }
/** /// @notice Checks whether an address has a valid truthy attestation.
* @notice Checks whether an address has a valid truthy attestation. /// Any attestation val other than bytes32("") is considered truthy.
* Any attestation val other than bytes32("") is considered truthy. /// @param _creator Address that made the attestation.
* /// @param _about Address attestation is about.
* @param _creator Address that made the attestation. /// @param _key Key of the attestation.
* @param _about Address attestation is about. /// @return valid_ Whether or not the address has a valid truthy attestation.
* @param _key Key of the attestation.
*
* @return Whether or not the address has a valid truthy attestation.
*/
function _hasValidAttestation( function _hasValidAttestation(
address _creator, address _creator,
address _about, address _about,
bytes32 _key bytes32 _key
) internal view returns (bool) { ) internal view returns (bool valid_) {
return ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0; valid_ = ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0;
} }
} }
...@@ -9,144 +9,108 @@ import { ...@@ -9,144 +9,108 @@ import {
EIP712Upgradeable EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
/** /// @custom:upgradeable
* @custom:upgradeable /// @title OptimistInviter
* @title OptimistInviter /// @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite"
* @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite" /// attestations. Accounts that have invites can issue signatures that allow other
* attestations. Accounts that have invites can issue signatures that allow other /// accounts to claim an invite. The invitee uses a claim and reveal flow to claim the
* accounts to claim an invite. The invitee uses a claim and reveal flow to claim the /// invite to an address of their choosing.
* invite to an address of their choosing. ///
* /// Parties involved:
* Parties involved: /// 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites
* 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites /// 2) issuer: account that is allowed to issue invites
* 2) issuer: account that is allowed to issue invites /// 3) claimer: account that receives the invites
* 3) claimer: account that receives the invites ///
* /// Flow:
* Flow: /// 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number
* 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number /// of invites, and also creates a "optimist.can-invite" attestation for the issuer
* of invites, and also creates a "optimist.can-invite" attestation for the issuer /// 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature
* 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature /// 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature
* 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature /// to the recipient
* to the recipient /// 4) claimer chooses an address they want to receive the invite on
* 4) claimer chooses an address they want to receive the invite on /// 5) claimer commits the hash of the address they want to receive the invite on and the
* 5) claimer commits the hash of the address they want to receive the invite on and the /// received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature))
* received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature)) /// using the commitInvite function
* using the commitInvite function /// 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass.
* 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass. /// 7) claimer reveals the plaintext ClaimableInvite and the signature using the
* 7) claimer reveals the plaintext ClaimableInvite and the signature using the /// claimInvite function, receiving the "optimist.can-mint-from-invite" attestation
* claimInvite function, receiving the "optimist.can-mint-from-invite" attestation
*/
contract OptimistInviter is Semver, EIP712Upgradeable { contract OptimistInviter is Semver, EIP712Upgradeable {
/** /// @notice Emitted when an invite is claimed.
* @notice Emitted when an invite is claimed. /// @param issuer Address that issued the signature.
* /// @param claimer Address that claimed the invite.
* @param issuer Address that issued the signature.
* @param claimer Address that claimed the invite.
*/
event InviteClaimed(address indexed issuer, address indexed claimer); event InviteClaimed(address indexed issuer, address indexed claimer);
/** /// @notice Version used for the EIP712 domain separator. This version is separated from the
* @notice Version used for the EIP712 domain separator. This version is separated from the /// contract semver because the EIP712 domain separator is used to sign messages, and
* contract semver because the EIP712 domain separator is used to sign messages, and /// changing the domain separator invalidates all existing signatures. We should only
* changing the domain separator invalidates all existing signatures. We should only /// bump this version if we make a major change to the signature scheme.
* bump this version if we make a major change to the signature scheme.
*/
string public constant EIP712_VERSION = "1.0.0"; string public constant EIP712_VERSION = "1.0.0";
/** /// @notice EIP712 typehash for the ClaimableInvite type.
* @notice EIP712 typehash for the ClaimableInvite type.
*/
bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = bytes32 public constant CLAIMABLE_INVITE_TYPEHASH =
keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); keccak256("ClaimableInvite(address issuer,bytes32 nonce)");
/** /// @notice Attestation key for that signals that an account was allowed to issue invites
* @notice Attestation key for that signals that an account was allowed to issue invites
*/
bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite"); bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite");
/** /// @notice Granter who can set accounts' invite counts.
* @notice Granter who can set accounts' invite counts.
*/
address public immutable INVITE_GRANTER; address public immutable INVITE_GRANTER;
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; AttestationStation public immutable ATTESTATION_STATION;
/** /// @notice Minimum age of a commitment (in seconds) before it can be revealed using
* @notice Minimum age of a commitment (in seconds) before it can be revealed using claimInvite. /// claimInvite. Currently set to 60 seconds.
* Currently set to 60 seconds. ///
* /// Prevents an attacker from front-running a commitment by taking the signature in the
* Prevents an attacker from front-running a commitment by taking the signature in the /// claimInvite call and quickly committing and claiming it before the the claimer's
* claimInvite call and quickly committing and claiming it before the the claimer's /// transaction succeeds. With this, frontrunning a commitment requires that an attacker
* transaction succeeds. With this, frontrunning a commitment requires that an attacker /// be able to prevent the honest claimer's claimInvite transaction from being included
* be able to prevent the honest claimer's claimInvite transaction from being included /// for this long.
* for this long.
*/
uint256 public constant MIN_COMMITMENT_PERIOD = 60; uint256 public constant MIN_COMMITMENT_PERIOD = 60;
/** /// @notice Struct that represents a claimable invite that will be signed by the issuer.
* @notice Struct that represents a claimable invite that will be signed by the issuer. /// @custom:field issuer Address that issued the signature. Reason this is explicitly included,
* /// and not implicitly assumed to be the recovered address from the
* @custom:field issuer Address that issued the signature. Reason this is explicitly included, /// signature is that the issuer may be using a ERC-1271 compatible
* and not implicitly assumed to be the recovered address from the /// contract wallet, where the recovered address is not the same as the
* signature is that the issuer may be using a ERC-1271 compatible /// issuer, or the signature is not an ECDSA signature at all.
* contract wallet, where the recovered address is not the same as the /// @custom:field nonce Pseudorandom nonce to prevent replay attacks.
* issuer, or the signature is not an ECDSA signature at all.
* @custom:field nonce Pseudorandom nonce to prevent replay attacks.
*/
struct ClaimableInvite { struct ClaimableInvite {
address issuer; address issuer;
bytes32 nonce; bytes32 nonce;
} }
/** /// @notice Maps from hashes to the timestamp when they were committed.
* @notice Maps from hashes to the timestamp when they were committed.
*/
mapping(bytes32 => uint256) public commitmentTimestamps; mapping(bytes32 => uint256) public commitmentTimestamps;
/** /// @notice Maps from addresses to nonces to whether or not they have been used.
* @notice Maps from addresses to nonces to whether or not they have been used.
*/
mapping(address => mapping(bytes32 => bool)) public usedNonces; mapping(address => mapping(bytes32 => bool)) public usedNonces;
/** /// @notice Maps from addresses to number of invites they have.
* @notice Maps from addresses to number of invites they have.
*/
mapping(address => uint256) public inviteCounts; mapping(address => uint256) public inviteCounts;
/** /// @custom:semver 1.0.0
* @custom:semver 1.0.0 /// @param _inviteGranter Address of the invite granter.
* /// @param _attestationStation Address of the AttestationStation contract.
* @param _inviteGranter Address of the invite granter.
* @param _attestationStation Address of the AttestationStation contract.
*/
constructor(address _inviteGranter, AttestationStation _attestationStation) Semver(1, 0, 0) { constructor(address _inviteGranter, AttestationStation _attestationStation) Semver(1, 0, 0) {
INVITE_GRANTER = _inviteGranter; INVITE_GRANTER = _inviteGranter;
ATTESTATION_STATION = _attestationStation; ATTESTATION_STATION = _attestationStation;
} }
/** /// @notice Initializes this contract, setting the EIP712 context.
* @notice Initializes this contract, setting the EIP712 context. /// Only update the EIP712_VERSION when there is a change to the signature scheme.
* /// After the EIP712 version is changed, any signatures issued off-chain but not
* Only update the EIP712_VERSION when there is a change to the signature scheme. /// claimed yet will no longer be accepted by the claimInvite function. Please make
* After the EIP712 version is changed, any signatures issued off-chain but not /// sure to notify the issuers that they must re-issue their invite signatures.
* claimed yet will no longer be accepted by the claimInvite function. Please make /// @param _name Contract name.
* sure to notify the issuers that they must re-issue their invite signatures.
*
* @param _name Contract name.
*/
function initialize(string memory _name) public initializer { function initialize(string memory _name) public initializer {
__EIP712_init(_name, EIP712_VERSION); __EIP712_init(_name, EIP712_VERSION);
} }
/** /// @notice Allows invite granter to set the number of invites an address has.
* @notice Allows invite granter to set the number of invites an address has. /// @param _accounts An array of accounts to update the invite counts of.
* /// @param _inviteCount Number of invites to set to.
* @param _accounts An array of accounts to update the invite counts of.
* @param _inviteCount Number of invites to set to.
*/
function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public { function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public {
// Only invite granter can grant invites // Only invite granter can grant invites
require( require(
...@@ -178,25 +142,21 @@ contract OptimistInviter is Semver, EIP712Upgradeable { ...@@ -178,25 +142,21 @@ contract OptimistInviter is Semver, EIP712Upgradeable {
ATTESTATION_STATION.attest(attestations); ATTESTATION_STATION.attest(attestations);
} }
/** /// @notice Allows anyone (but likely the claimer) to commit a received signature along with the
* @notice Allows anyone (but likely the claimer) to commit a received signature along with the /// address to claim to.
* address to claim to. ///
* /// Before calling this function, the claimer should have received a signature from the
* Before calling this function, the claimer should have received a signature from the /// issuer off-chain. The claimer then calls this function with the hash of the
* issuer off-chain. The claimer then calls this function with the hash of the /// claimer's address and the received signature. This is necessary to prevent
* claimer's address and the received signature. This is necessary to prevent /// front-running when the invitee is claiming the invite. Without a commit and reveal
* front-running when the invitee is claiming the invite. Without a commit and reveal /// scheme, anyone who is watching the mempool can take the signature being submitted
* scheme, anyone who is watching the mempool can take the signature being submitted /// and front run the transaction to claim the invite to their own address.
* and front run the transaction to claim the invite to their own address. ///
* /// The same commitment can only be made once, and the function reverts if the
* The same commitment can only be made once, and the function reverts if the /// commitment has already been made. This prevents griefing where a malicious party can
* commitment has already been made. This prevents griefing where a malicious party can /// prevent the original claimer from being able to claimInvite.
* prevent the original claimer from being able to claimInvite. /// @param _commitment A hash of the claimer and signature concatenated.
* /// keccak256(abi.encode(_claimer, _signature))
*
* @param _commitment A hash of the claimer and signature concatenated.
* keccak256(abi.encode(_claimer, _signature))
*/
function commitInvite(bytes32 _commitment) public { function commitInvite(bytes32 _commitment) public {
// Check that the commitment hasn't already been made. This prevents griefing where // Check that the commitment hasn't already been made. This prevents griefing where
// a malicious party continuously re-submits the same commitment, preventing the original // a malicious party continuously re-submits the same commitment, preventing the original
...@@ -206,23 +166,19 @@ contract OptimistInviter is Semver, EIP712Upgradeable { ...@@ -206,23 +166,19 @@ contract OptimistInviter is Semver, EIP712Upgradeable {
commitmentTimestamps[_commitment] = block.timestamp; commitmentTimestamps[_commitment] = block.timestamp;
} }
/** /// @notice Allows anyone to reveal a commitment and claim an invite.
* @notice Allows anyone to reveal a commitment and claim an invite. /// The hash, keccak256(abi.encode(_claimer, _signature)), should have been already
* /// committed using commitInvite. Before issuing the "optimist.can-mint-from-invite"
* The hash, keccak256(abi.encode(_claimer, _signature)), should have been already /// attestation, this function checks that
* committed using commitInvite. Before issuing the "optimist.can-mint-from-invite" /// 1) the hash corresponding to the _claimer and the _signature was committed
* attestation, this function checks that /// 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made.
* 1) the hash corresponding to the _claimer and the _signature was committed /// 3) the _signature is signed correctly by the issuer
* 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made. /// 4) the _signature hasn't already been used to claim an invite before
* 3) the _signature is signed correctly by the issuer /// 5) the _signature issuer has not used up all of their invites
* 4) the _signature hasn't already been used to claim an invite before /// This function doesn't require that the _claimer is calling this function.
* 5) the _signature issuer has not used up all of their invites /// @param _claimer Address that will be granted the invite.
* This function doesn't require that the _claimer is calling this function. /// @param _claimableInvite ClaimableInvite struct containing the issuer and nonce.
* /// @param _signature Signature signed over the claimable invite.
* @param _claimer Address that will be granted the invite.
* @param _claimableInvite ClaimableInvite struct containing the issuer and nonce.
* @param _signature Signature signed over the claimable invite.
*/
function claimInvite( function claimInvite(
address _claimer, address _claimer,
ClaimableInvite calldata _claimableInvite, ClaimableInvite calldata _claimableInvite,
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/** /// @title OptimistConstants
* @title OptimistConstants /// @notice Library for storing Optimist related constants that are shared in multiple contracts.
* @notice Library for storing Optimist related constants that are shared in multiple contracts.
*/
library OptimistConstants { library OptimistConstants {
/** /// @notice Attestation key issued by OptimistInviter allowing the attested account to mint.
* @notice Attestation key issued by OptimistInviter allowing the attested account to mint.
*/
bytes32 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY = bytes32 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY =
bytes32("optimist.can-mint-from-invite"); bytes32("optimist.can-mint-from-invite");
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment