Skip to content

Commit

Permalink
Refactor Discount Validator to be an abstract contract supporting sta…
Browse files Browse the repository at this point in the history
…te writes in validation
  • Loading branch information
stevieraykatz committed Oct 29, 2024
1 parent 4e64f80 commit c548aef
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 39 deletions.
36 changes: 18 additions & 18 deletions src/L2/RegistrarController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -232,25 +232,11 @@ contract RegistrarController is Ownable {
_;
}

/// @notice Decorator for validating discounted registrations.
/// @notice Decorator for validating a user for discounted registration.
///
/// @dev Validates that:
/// 1. That the registrant has not already registered with a discount
/// 2. That the discount is `active`
/// 3. That the associated `discountValidator` returns true when `isValidDiscountRegistration` is called.
///
/// @param discountKey The uuid of the discount.
/// @param validationData The associated validation data for this discount registration.
modifier validDiscount(bytes32 discountKey, bytes calldata validationData) {
/// @dev Validates that that the registrant has not already registered with a discount
modifier discountAvailable() {
if (discountedRegistrants[msg.sender]) revert AlreadyRegisteredWithDiscount(msg.sender);
DiscountDetails memory details = discounts[discountKey];

if (!details.active) revert InactiveDiscount(discountKey);

IDiscountValidator validator = IDiscountValidator(details.discountValidator);
if (!validator.isValidDiscountRegistration(msg.sender, validationData)) {
revert InvalidDiscount(discountKey, validationData);
}
_;
}

Expand Down Expand Up @@ -459,9 +445,11 @@ contract RegistrarController is Ownable {
function discountedRegister(RegisterRequest calldata request, bytes32 discountKey, bytes calldata validationData)
public
payable
validDiscount(discountKey, validationData)
validRegistration(request)
discountAvailable
{
_validateDiscount(discountKey, validationData);

uint256 price = discountedRegisterPrice(request.name, request.duration, discountKey);

_validatePayment(price);
Expand Down Expand Up @@ -593,6 +581,18 @@ contract RegistrarController is Ownable {
active ? activeDiscounts.add(key) : activeDiscounts.remove(key);
}

/// Validates that:
/// 1. That the discount is `active`
/// 2. That the associated `discountValidator` returns true when `isValidDiscountRegistration` is called.
function _validateDiscount(bytes32 discountKey, bytes calldata validationData) internal {
DiscountDetails memory details = discounts[discountKey];

IDiscountValidator validator = IDiscountValidator(details.discountValidator);
// if (!validator.validateDiscountRegistration(msg.sender, validationData)) {
// revert InvalidDiscount(discountKey, validationData);
// }
}

/// @notice Allows anyone to withdraw the eth accumulated on this contract back to the `paymentReceiver`.
function withdrawETH() public {
(bool sent,) = payable(paymentReceiver).call{value: (address(this).balance)}("");
Expand Down
6 changes: 3 additions & 3 deletions src/L2/discounts/AttestationValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {AttestationVerifier} from "verifications/libraries/AttestationVerifier.s
import {IAttestationIndexer} from "verifications/interfaces/IAttestationIndexer.sol";
import {Ownable} from "solady/auth/Ownable.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";
import {SybilResistanceVerifier} from "src/lib/SybilResistanceVerifier.sol";

/// @title Discount Validator for: Coinbase Attestation Validator
Expand All @@ -17,7 +17,7 @@ import {SybilResistanceVerifier} from "src/lib/SybilResistanceVerifier.sol";
/// https://github.com/coinbase/verifications
///
/// @author Coinbase (https://github.com/base-org/usernames)
contract AttestationValidator is Ownable, AttestationAccessControl, IDiscountValidator {
contract AttestationValidator is Ownable, AttestationAccessControl, DiscountValidator {
/// @dev The attestation service signer.
address signer;

Expand Down Expand Up @@ -52,7 +52,7 @@ contract AttestationValidator is Ownable, AttestationAccessControl, IDiscountVal
/// @param validationData opaque bytes for performing the validation.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata validationData) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata validationData) public override view returns (bool) {
AttestationVerifier.verifyAttestation(_getAttestation(claimer, schemaID));

return SybilResistanceVerifier.verifySignature(signer, claimer, validationData);
Expand Down
6 changes: 3 additions & 3 deletions src/L2/discounts/CBIdDiscountValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ pragma solidity ^0.8.23;
import {MerkleProofLib} from "lib/solady/src/utils/MerkleProofLib.sol";
import {Ownable} from "solady/auth/Ownable.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";

/// @title Discount Validator for: cb.id
///
/// @notice Implements a simple Merkle Proof validator checking that the claimant is in the stored merkle tree.
///
/// @author Coinbase
contract CBIdDiscountValidator is Ownable, IDiscountValidator {
contract CBIdDiscountValidator is Ownable, DiscountValidator {
/// @dev merkle tree root
bytes32 public root;

Expand All @@ -35,7 +35,7 @@ contract CBIdDiscountValidator is Ownable, IDiscountValidator {
/// @param validationData opaque bytes for performing the validation.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata validationData) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata validationData) public view override returns (bool) {
(bytes32[] memory proof) = abi.decode(validationData, (bytes32[]));
return MerkleProofLib.verify(proof, root, keccak256(abi.encodePacked(claimer)));
}
Expand Down
6 changes: 3 additions & 3 deletions src/L2/discounts/CouponDiscountValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ pragma solidity ^0.8.23;
import {ECDSA} from "solady/utils/ECDSA.sol";
import {Ownable} from "solady/auth/Ownable.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";

/// @title Discount Validator for: Coupons
///
/// @notice Implements a signature-based discount validation on unique coupon codes.
///
/// @author Coinbase (https://github.com/base-org/usernames)
contract CouponDiscountValidator is Ownable, IDiscountValidator {
contract CouponDiscountValidator is Ownable, DiscountValidator {
/// @notice Thrown when setting a critical address to the zero-address.
error NoZeroAddress();

Expand Down Expand Up @@ -46,7 +46,7 @@ contract CouponDiscountValidator is Ownable, IDiscountValidator {
/// @param validationData opaque bytes for performing the validation.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata validationData) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata validationData) public view override returns (bool) {
(uint64 expiry, bytes32 uuid, bytes memory sig) = abi.decode(validationData, (uint64, bytes32, bytes));
if (expiry < block.timestamp) revert SignatureExpired();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ pragma solidity ^0.8.23;
///
/// @notice Common interface which all Discount Validators must implement.
/// The logic specific to each integration must ultimately be consumable as the `bool` returned from
/// `isValidDiscountRegistration`.
interface IDiscountValidator {
/// `isValidDiscountRegistration`. Then, upon registration, the integrator should call `validateDiscountRegistration`
/// allowing discount-specific state writes to occur.
abstract contract DiscountValidator {
/// @notice Thrown when the specified discount's validator does not accept the discount for the sender.
///
/// @param claimer The discount being accessed.
/// @param data The associated `validationData`.
error InvalidDiscount(address claimer, bytes data);

/// @notice Required implementation for compatibility with IDiscountValidator.
///
/// @dev Each implementation will have unique requirements for the data necessary to perform
Expand All @@ -17,5 +24,9 @@ interface IDiscountValidator {
/// @param validationData opaque bytes for performing the validation.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata validationData) external returns (bool);
function isValidDiscountRegistration(address claimer, bytes calldata validationData) public virtual view returns (bool);

function validateDiscountRegistration(address claimer, bytes calldata validationData) external virtual view {
if(!isValidDiscountRegistration(claimer, validationData)) revert InvalidDiscount(claimer, validationData);
}
}
6 changes: 3 additions & 3 deletions src/L2/discounts/ERC1155DiscountValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ pragma solidity ^0.8.23;

import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";

/// @title Discount Validator for: ERC1155 NFTs
///
/// @notice Implements an NFT ownership validator for a stored `tokenId` for an ERC1155 `token` contract.
/// This discount validator should only be used for "soul-bound" tokens.
///
/// @author Coinbase (https://github.com/base-org/usernames)
contract ERC1155DiscountValidator is IDiscountValidator {
contract ERC1155DiscountValidator is DiscountValidator {
/// @notice The ERC1155 token contract to validate against.
IERC1155 immutable token;

Expand All @@ -35,7 +35,7 @@ contract ERC1155DiscountValidator is IDiscountValidator {
/// @param claimer the discount claimer's address.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata) public view override returns (bool) {
return (token.balanceOf(claimer, tokenId) > 0);
}
}
6 changes: 3 additions & 3 deletions src/L2/discounts/ERC721DiscountValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ pragma solidity ^0.8.23;

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";

/// @title Discount Validator for: ERC721 NFTs
///
/// @notice Implements an NFT ownership validator for a ERC721 `token` contract.
/// This discount validator should only be used for "soul-bound" tokens.
///
/// @author Coinbase (https://github.com/base-org/usernames)
contract ERC721DiscountValidator is IDiscountValidator {
contract ERC721DiscountValidator is DiscountValidator {
/// @notice The ERC721 token contract to validate against.
IERC721 immutable token;

Expand All @@ -30,7 +30,7 @@ contract ERC721DiscountValidator is IDiscountValidator {
/// @param claimer the discount claimer's address.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata) public view override returns (bool) {
return (token.balanceOf(claimer) > 0);
}
}
6 changes: 3 additions & 3 deletions src/L2/discounts/TalentProtocolDiscountValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity ^0.8.23;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Ownable} from "solady/auth/Ownable.sol";

import {IDiscountValidator} from "src/L2/interface/IDiscountValidator.sol";
import {DiscountValidator} from "./DiscountValidator.sol";

/// @title Discount Validator for: Talent Protocol Builder Score
///
/// @notice Enables discounts for users who have minted their Talent Protocol Builder Score .
/// Discounts are granted based on the claimer having some score higher than this contract's `threshold`.
///
/// @author Coinbase (https://github.com/base-org/usernames)
contract TalentProtocolDiscountValidator is IDiscountValidator, Ownable {
contract TalentProtocolDiscountValidator is DiscountValidator, Ownable {
/// @notice Thrown when setting a critical address to the zero-address.
error NoZeroAddress();

Expand Down Expand Up @@ -54,7 +54,7 @@ contract TalentProtocolDiscountValidator is IDiscountValidator, Ownable {
/// @param claimer the discount claimer's address.
///
/// @return `true` if the validation data provided is determined to be valid for the specified claimer, else `false`.
function isValidDiscountRegistration(address claimer, bytes calldata) external view returns (bool) {
function isValidDiscountRegistration(address claimer, bytes calldata) public view override returns (bool) {
return (talentProtocol.getScoreByAddress(claimer) >= threshold);
}
}
Expand Down

0 comments on commit c548aef

Please sign in to comment.