Skip to content

Commit

Permalink
feat: add SCRHoldingBadge
Browse files Browse the repository at this point in the history
  • Loading branch information
zimpha committed Nov 22, 2024
1 parent 92e08a8 commit f7b29f5
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 6 deletions.
4 changes: 4 additions & 0 deletions src/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ string constant SCROLL_BADGE_SCHEMA = "address badge, bytes payload";
function decodeBadgeData(bytes memory data) pure returns (address, bytes memory) {
return abi.decode(data, (address, bytes));
}

function encodeBadgeData(address badge, bytes memory payload) pure returns (bytes memory) {
return abi.encode(badge, payload);
}
136 changes: 136 additions & 0 deletions src/badge/examples/SCRHoldingBadge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Attestation} from "@eas/contracts/IEAS.sol";
import {NO_EXPIRATION_TIME} from "@eas/contracts/Common.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

import {IScrollBadgeResolver} from "../../interfaces/IScrollBadgeResolver.sol";
import {IScrollBadge, IScrollSelfAttestationBadge} from "../../interfaces/IScrollSelfAttestationBadge.sol";
import {encodeBadgeData} from "../../Common.sol";
import {ScrollBadge} from "../ScrollBadge.sol";
import {ScrollBadgeCustomPayload} from "../extensions/ScrollBadgeCustomPayload.sol";
import {ScrollBadgeDefaultURI} from "../extensions/ScrollBadgeDefaultURI.sol";

string constant SCR_HOLDING_BADGE_SCHEMA = "uint256 level";

function decodePayloadData(bytes memory data) pure returns (uint256) {
return abi.decode(data, (uint256));
}

/// @title SCRHoldingBadge
/// @notice A badge that represents user's SCR holding amount.
contract SCRHoldingBadge is ScrollBadgeCustomPayload, ScrollBadgeDefaultURI, Ownable, IScrollSelfAttestationBadge {
uint256 private constant LEVEL_ONE_SCR_AMOUNT = 1 ether;
uint256 private constant LEVEL_TWO_SCR_AMOUNT = 10 ether;
uint256 private constant LEVEL_THREE_SCR_AMOUNT = 100 ether;
uint256 private constant LEVEL_FOUR_SCR_AMOUNT = 1000 ether;
uint256 private constant LEVEL_FIVE_SCR_AMOUNT = 10000 ether;
uint256 private constant LEVEL_SIX_SCR_AMOUNT = 100000 ether;

/// @notice The address of SCR token.
address public immutable scr;

constructor(
address resolver_,
string memory baseTokenURI_,
address scr_
) ScrollBadge(resolver_) ScrollBadgeDefaultURI(baseTokenURI_) {
scr = scr_;
}

/// @notice Update the base token URI.
/// @param baseTokenURI_ The new base token URI.
function updateBaseTokenURI(string memory baseTokenURI_) external onlyOwner {
defaultBadgeURI = baseTokenURI_;
}

/// @inheritdoc ScrollBadge
function onIssueBadge(
Attestation calldata
) internal virtual override(ScrollBadge, ScrollBadgeCustomPayload) returns (bool) {
return false;
}

/// @inheritdoc ScrollBadge
function onRevokeBadge(
Attestation calldata
) internal virtual override(ScrollBadge, ScrollBadgeCustomPayload) returns (bool) {
return false;
}

/// @inheritdoc ScrollBadge
function badgeTokenURI(
bytes32 uid
) public view override(IScrollBadge, ScrollBadge, ScrollBadgeDefaultURI) returns (string memory) {
return ScrollBadgeDefaultURI.badgeTokenURI(uid);
}

/// @inheritdoc ScrollBadgeDefaultURI
function getBadgeTokenURI(bytes32 uid) internal view override returns (string memory) {
Attestation memory attestation = getAndValidateBadge(uid);
bytes memory payload = getPayload(attestation);
uint256 year = decodePayloadData(payload);

return string(abi.encodePacked(defaultBadgeURI, Strings.toString(year), ".json"));
}

/// @inheritdoc ScrollBadgeCustomPayload
function getSchema() public pure override returns (string memory) {
return SCR_HOLDING_BADGE_SCHEMA;
}

/// @inheritdoc IScrollSelfAttestationBadge
function getBadgeId() external pure returns (uint256) {
return 0;
}

/// @inheritdoc IScrollSelfAttestationBadge
///
/// @dev The uid encoding should be
/// ```text
/// [ address | badge id | customized data ]
/// [ 160 bits | 32 bits | 64 bits ]
/// [LSB MSB]
/// ```
/// The *badge id* and the *customized data* should both be zero.
function getAttestation(bytes32 uid) external view override returns (Attestation memory attestation) {
// invalid uid, return empty badge
if ((uint256(uid) >> 160) > 0) return attestation;

// extract badge recipient from uid
address recipient;
assembly {
recipient := and(uid, 0xffffffffffffffffffffffffffffffffffffffff)
}

// compute payload
uint256 level;
uint256 balance = IERC20(scr).balanceOf(recipient);
// not hold enough SCR, return empty badge
if (balance < LEVEL_ONE_SCR_AMOUNT) return attestation;
else if (balance < LEVEL_TWO_SCR_AMOUNT) level = 1;
else if (balance < LEVEL_THREE_SCR_AMOUNT) level = 2;
else if (balance < LEVEL_FOUR_SCR_AMOUNT) level = 3;
else if (balance < LEVEL_FIVE_SCR_AMOUNT) level = 4;
else if (balance < LEVEL_SIX_SCR_AMOUNT) level = 5;
else level = 6;
bytes memory payload = abi.encode(level);

// fill data in Attestation
attestation.uid = uid;
attestation.schema = IScrollBadgeResolver(resolver).schema();
attestation.time = uint64(block.timestamp);
attestation.expirationTime = NO_EXPIRATION_TIME;
attestation.refUID = bytes32(0);
attestation.recipient = recipient;
attestation.attester = address(this);
attestation.revocable = false;
attestation.data = encodeBadgeData(address(this), payload);

return attestation;
}
}
2 changes: 1 addition & 1 deletion src/badge/extensions/ScrollBadgeDefaultURI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract contract ScrollBadgeDefaultURI is ScrollBadge {
}

/// @inheritdoc ScrollBadge
function badgeTokenURI(bytes32 uid) public view override returns (string memory) {
function badgeTokenURI(bytes32 uid) public view override virtual returns (string memory) {
if (uid == bytes32(0)) {
return defaultBadgeURI;
}
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/IScrollBadgeResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ interface IScrollBadgeResolver {

/// @notice Return the Scroll badge attestation schema.
/// @return The GUID of the Scroll badge attestation schema.
function schema() external returns (bytes32);
function schema() external view returns (bytes32);

/// @notice The profile registry contract.
/// @return The address of the profile registry.
function registry() external returns (address);
function registry() external view returns (address);

/// @notice The global EAS contract.
/// @return The address of the global EAS contract.
function eas() external returns (address);
function eas() external view returns (address);

/// @notice Validate and return a Scroll badge attestation.
/// @param uid The attestation UID.
Expand Down
17 changes: 17 additions & 0 deletions src/interfaces/IScrollSelfAttestationBadge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Attestation} from "@eas/contracts/IEAS.sol";

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

interface IScrollSelfAttestationBadge is IScrollBadge {
/// @notice Return the unique id of this badge.
function getBadgeId() external view returns (uint256);

/// @notice Returns an existing attestation by UID.
/// @param uid The UID of the attestation to retrieve.
/// @return The attestation data members.
function getAttestation(bytes32 uid) external view returns (Attestation memory);
}
38 changes: 36 additions & 2 deletions src/resolver/ScrollBadgeResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {IProfile} from "../interfaces/IProfile.sol";
import {IProfileRegistry} from "../interfaces/IProfileRegistry.sol";
import {IScrollBadge} from "../interfaces/IScrollBadge.sol";
import {IScrollBadgeResolver} from "../interfaces/IScrollBadgeResolver.sol";
import {IScrollSelfAttestationBadge} from "../interfaces/IScrollSelfAttestationBadge.sol";
import {SCROLL_BADGE_SCHEMA, decodeBadgeData} from "../Common.sol";
import {ScrollBadgeResolverWhitelist} from "./ScrollBadgeResolverWhitelist.sol";

Expand Down Expand Up @@ -49,8 +50,19 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
/// @inheritdoc IScrollBadgeResolver
bytes32 public schema;

/// @notice The list of self attested badges, mapping from badge id to badge address.
/// @dev This is a list of badges with special needs which EAS cannot satisfy, such as
/// auto attest/revoke badge based on certain token holding amount.
/// The uid for the badge is customized in the following way:
/// ```text
/// [ address | badge id | customized data ]
/// [ 160 bits | 32 bits | 64 bits ]
/// [LSB MSB]
/// ```
mapping(uint256 => address) public selfAttestedBadges;

// Storage slots reserved for future upgrades.
uint256[49] private __gap;
uint256[48] private __gap;

/**
*
Expand Down Expand Up @@ -165,8 +177,19 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
function getAndValidateBadge(bytes32 uid) external view returns (Attestation memory) {
Attestation memory attestation = _eas.getAttestation(uid);

// if we cannot find the badge in EAS, try self attestation
if (attestation.uid == EMPTY_UID) {
revert AttestationNotFound(uid);
// extract badge address from uid and do self attestation
uint256 badgeId = uint256(uid) >> 160 & 0xffffffff;
address badgeAddr = selfAttestedBadges[badgeId];
if (badgeAddr != address(0)) {
attestation = IScrollSelfAttestationBadge(badgeAddr).getAttestation(uid);
}
if (attestation.uid == EMPTY_UID) {
revert AttestationNotFound(uid);
} else {
return attestation;
}
}

if (attestation.schema != schema) {
Expand All @@ -184,6 +207,17 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
return attestation;
}

/**
*
* Restricted Functions *
*
*/

/// @notice Update the address of a self attested badge.
function updateSelfAttestedBadge(uint256 badgeId, address badgeAddress) external onlyOwner {
selfAttestedBadges[badgeId] = badgeAddress;
}

/**
*
* Internal Functions *
Expand Down

0 comments on commit f7b29f5

Please sign in to comment.