Skip to content

Commit f7b29f5

Browse files
committed
feat: add SCRHoldingBadge
1 parent 92e08a8 commit f7b29f5

File tree

6 files changed

+197
-6
lines changed

6 files changed

+197
-6
lines changed

src/Common.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ string constant SCROLL_BADGE_SCHEMA = "address badge, bytes payload";
99
function decodeBadgeData(bytes memory data) pure returns (address, bytes memory) {
1010
return abi.decode(data, (address, bytes));
1111
}
12+
13+
function encodeBadgeData(address badge, bytes memory payload) pure returns (bytes memory) {
14+
return abi.encode(badge, payload);
15+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.19;
4+
5+
import {Attestation} from "@eas/contracts/IEAS.sol";
6+
import {NO_EXPIRATION_TIME} from "@eas/contracts/Common.sol";
7+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
8+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
10+
11+
import {IScrollBadgeResolver} from "../../interfaces/IScrollBadgeResolver.sol";
12+
import {IScrollBadge, IScrollSelfAttestationBadge} from "../../interfaces/IScrollSelfAttestationBadge.sol";
13+
import {encodeBadgeData} from "../../Common.sol";
14+
import {ScrollBadge} from "../ScrollBadge.sol";
15+
import {ScrollBadgeCustomPayload} from "../extensions/ScrollBadgeCustomPayload.sol";
16+
import {ScrollBadgeDefaultURI} from "../extensions/ScrollBadgeDefaultURI.sol";
17+
18+
string constant SCR_HOLDING_BADGE_SCHEMA = "uint256 level";
19+
20+
function decodePayloadData(bytes memory data) pure returns (uint256) {
21+
return abi.decode(data, (uint256));
22+
}
23+
24+
/// @title SCRHoldingBadge
25+
/// @notice A badge that represents user's SCR holding amount.
26+
contract SCRHoldingBadge is ScrollBadgeCustomPayload, ScrollBadgeDefaultURI, Ownable, IScrollSelfAttestationBadge {
27+
uint256 private constant LEVEL_ONE_SCR_AMOUNT = 1 ether;
28+
uint256 private constant LEVEL_TWO_SCR_AMOUNT = 10 ether;
29+
uint256 private constant LEVEL_THREE_SCR_AMOUNT = 100 ether;
30+
uint256 private constant LEVEL_FOUR_SCR_AMOUNT = 1000 ether;
31+
uint256 private constant LEVEL_FIVE_SCR_AMOUNT = 10000 ether;
32+
uint256 private constant LEVEL_SIX_SCR_AMOUNT = 100000 ether;
33+
34+
/// @notice The address of SCR token.
35+
address public immutable scr;
36+
37+
constructor(
38+
address resolver_,
39+
string memory baseTokenURI_,
40+
address scr_
41+
) ScrollBadge(resolver_) ScrollBadgeDefaultURI(baseTokenURI_) {
42+
scr = scr_;
43+
}
44+
45+
/// @notice Update the base token URI.
46+
/// @param baseTokenURI_ The new base token URI.
47+
function updateBaseTokenURI(string memory baseTokenURI_) external onlyOwner {
48+
defaultBadgeURI = baseTokenURI_;
49+
}
50+
51+
/// @inheritdoc ScrollBadge
52+
function onIssueBadge(
53+
Attestation calldata
54+
) internal virtual override(ScrollBadge, ScrollBadgeCustomPayload) returns (bool) {
55+
return false;
56+
}
57+
58+
/// @inheritdoc ScrollBadge
59+
function onRevokeBadge(
60+
Attestation calldata
61+
) internal virtual override(ScrollBadge, ScrollBadgeCustomPayload) returns (bool) {
62+
return false;
63+
}
64+
65+
/// @inheritdoc ScrollBadge
66+
function badgeTokenURI(
67+
bytes32 uid
68+
) public view override(IScrollBadge, ScrollBadge, ScrollBadgeDefaultURI) returns (string memory) {
69+
return ScrollBadgeDefaultURI.badgeTokenURI(uid);
70+
}
71+
72+
/// @inheritdoc ScrollBadgeDefaultURI
73+
function getBadgeTokenURI(bytes32 uid) internal view override returns (string memory) {
74+
Attestation memory attestation = getAndValidateBadge(uid);
75+
bytes memory payload = getPayload(attestation);
76+
uint256 year = decodePayloadData(payload);
77+
78+
return string(abi.encodePacked(defaultBadgeURI, Strings.toString(year), ".json"));
79+
}
80+
81+
/// @inheritdoc ScrollBadgeCustomPayload
82+
function getSchema() public pure override returns (string memory) {
83+
return SCR_HOLDING_BADGE_SCHEMA;
84+
}
85+
86+
/// @inheritdoc IScrollSelfAttestationBadge
87+
function getBadgeId() external pure returns (uint256) {
88+
return 0;
89+
}
90+
91+
/// @inheritdoc IScrollSelfAttestationBadge
92+
///
93+
/// @dev The uid encoding should be
94+
/// ```text
95+
/// [ address | badge id | customized data ]
96+
/// [ 160 bits | 32 bits | 64 bits ]
97+
/// [LSB MSB]
98+
/// ```
99+
/// The *badge id* and the *customized data* should both be zero.
100+
function getAttestation(bytes32 uid) external view override returns (Attestation memory attestation) {
101+
// invalid uid, return empty badge
102+
if ((uint256(uid) >> 160) > 0) return attestation;
103+
104+
// extract badge recipient from uid
105+
address recipient;
106+
assembly {
107+
recipient := and(uid, 0xffffffffffffffffffffffffffffffffffffffff)
108+
}
109+
110+
// compute payload
111+
uint256 level;
112+
uint256 balance = IERC20(scr).balanceOf(recipient);
113+
// not hold enough SCR, return empty badge
114+
if (balance < LEVEL_ONE_SCR_AMOUNT) return attestation;
115+
else if (balance < LEVEL_TWO_SCR_AMOUNT) level = 1;
116+
else if (balance < LEVEL_THREE_SCR_AMOUNT) level = 2;
117+
else if (balance < LEVEL_FOUR_SCR_AMOUNT) level = 3;
118+
else if (balance < LEVEL_FIVE_SCR_AMOUNT) level = 4;
119+
else if (balance < LEVEL_SIX_SCR_AMOUNT) level = 5;
120+
else level = 6;
121+
bytes memory payload = abi.encode(level);
122+
123+
// fill data in Attestation
124+
attestation.uid = uid;
125+
attestation.schema = IScrollBadgeResolver(resolver).schema();
126+
attestation.time = uint64(block.timestamp);
127+
attestation.expirationTime = NO_EXPIRATION_TIME;
128+
attestation.refUID = bytes32(0);
129+
attestation.recipient = recipient;
130+
attestation.attester = address(this);
131+
attestation.revocable = false;
132+
attestation.data = encodeBadgeData(address(this), payload);
133+
134+
return attestation;
135+
}
136+
}

src/badge/extensions/ScrollBadgeDefaultURI.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ abstract contract ScrollBadgeDefaultURI is ScrollBadge {
1414
}
1515

1616
/// @inheritdoc ScrollBadge
17-
function badgeTokenURI(bytes32 uid) public view override returns (string memory) {
17+
function badgeTokenURI(bytes32 uid) public view override virtual returns (string memory) {
1818
if (uid == bytes32(0)) {
1919
return defaultBadgeURI;
2020
}

src/interfaces/IScrollBadgeResolver.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ interface IScrollBadgeResolver {
3232

3333
/// @notice Return the Scroll badge attestation schema.
3434
/// @return The GUID of the Scroll badge attestation schema.
35-
function schema() external returns (bytes32);
35+
function schema() external view returns (bytes32);
3636

3737
/// @notice The profile registry contract.
3838
/// @return The address of the profile registry.
39-
function registry() external returns (address);
39+
function registry() external view returns (address);
4040

4141
/// @notice The global EAS contract.
4242
/// @return The address of the global EAS contract.
43-
function eas() external returns (address);
43+
function eas() external view returns (address);
4444

4545
/// @notice Validate and return a Scroll badge attestation.
4646
/// @param uid The attestation UID.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.19;
4+
5+
import {Attestation} from "@eas/contracts/IEAS.sol";
6+
7+
import {IScrollBadge} from "./IScrollBadge.sol";
8+
9+
interface IScrollSelfAttestationBadge is IScrollBadge {
10+
/// @notice Return the unique id of this badge.
11+
function getBadgeId() external view returns (uint256);
12+
13+
/// @notice Returns an existing attestation by UID.
14+
/// @param uid The UID of the attestation to retrieve.
15+
/// @return The attestation data members.
16+
function getAttestation(bytes32 uid) external view returns (Attestation memory);
17+
}

src/resolver/ScrollBadgeResolver.sol

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {IProfile} from "../interfaces/IProfile.sol";
1212
import {IProfileRegistry} from "../interfaces/IProfileRegistry.sol";
1313
import {IScrollBadge} from "../interfaces/IScrollBadge.sol";
1414
import {IScrollBadgeResolver} from "../interfaces/IScrollBadgeResolver.sol";
15+
import {IScrollSelfAttestationBadge} from "../interfaces/IScrollSelfAttestationBadge.sol";
1516
import {SCROLL_BADGE_SCHEMA, decodeBadgeData} from "../Common.sol";
1617
import {ScrollBadgeResolverWhitelist} from "./ScrollBadgeResolverWhitelist.sol";
1718

@@ -49,8 +50,19 @@ contract ScrollBadgeResolver is IScrollBadgeResolver, SchemaResolver, ScrollBadg
4950
/// @inheritdoc IScrollBadgeResolver
5051
bytes32 public schema;
5152

53+
/// @notice The list of self attested badges, mapping from badge id to badge address.
54+
/// @dev This is a list of badges with special needs which EAS cannot satisfy, such as
55+
/// auto attest/revoke badge based on certain token holding amount.
56+
/// The uid for the badge is customized in the following way:
57+
/// ```text
58+
/// [ address | badge id | customized data ]
59+
/// [ 160 bits | 32 bits | 64 bits ]
60+
/// [LSB MSB]
61+
/// ```
62+
mapping(uint256 => address) public selfAttestedBadges;
63+
5264
// Storage slots reserved for future upgrades.
53-
uint256[49] private __gap;
65+
uint256[48] private __gap;
5466

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

180+
// if we cannot find the badge in EAS, try self attestation
168181
if (attestation.uid == EMPTY_UID) {
169-
revert AttestationNotFound(uid);
182+
// extract badge address from uid and do self attestation
183+
uint256 badgeId = uint256(uid) >> 160 & 0xffffffff;
184+
address badgeAddr = selfAttestedBadges[badgeId];
185+
if (badgeAddr != address(0)) {
186+
attestation = IScrollSelfAttestationBadge(badgeAddr).getAttestation(uid);
187+
}
188+
if (attestation.uid == EMPTY_UID) {
189+
revert AttestationNotFound(uid);
190+
} else {
191+
return attestation;
192+
}
170193
}
171194

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

210+
/**
211+
*
212+
* Restricted Functions *
213+
*
214+
*/
215+
216+
/// @notice Update the address of a self attested badge.
217+
function updateSelfAttestedBadge(uint256 badgeId, address badgeAddress) external onlyOwner {
218+
selfAttestedBadges[badgeId] = badgeAddress;
219+
}
220+
187221
/**
188222
*
189223
* Internal Functions *

0 commit comments

Comments
 (0)