diff --git a/src/Common.sol b/src/Common.sol index 613831e..466bc7d 100644 --- a/src/Common.sol +++ b/src/Common.sol @@ -17,28 +17,28 @@ error NotFound(); /// @notice A struct representing ECDSA signature data. struct Signature { - uint8 v; // The recovery ID. - bytes32 r; // The x-coordinate of the nonce R. - bytes32 s; // The signature data. + uint8 v; // The recovery ID. + bytes32 r; // The x-coordinate of the nonce R. + bytes32 s; // The signature data. } /// @notice A struct representing a single attestation. struct Attestation { - bytes32 uid; // A unique identifier of the attestation. - bytes32 schema; // The unique identifier of the schema. - uint64 time; // The time when the attestation was created (Unix timestamp). - uint64 expirationTime; // The time when the attestation expires (Unix timestamp). - uint64 revocationTime; // The time when the attestation was revoked (Unix timestamp). - bytes32 refUID; // The UID of the related attestation. - address recipient; // The recipient of the attestation. - address attester; // The attester/sender of the attestation. - bool revocable; // Whether the attestation is revocable. - bytes data; // Custom attestation data. + bytes32 uid; // A unique identifier of the attestation. + bytes32 schema; // The unique identifier of the schema. + uint64 time; // The time when the attestation was created (Unix timestamp). + uint64 expirationTime; // The time when the attestation expires (Unix timestamp). + uint64 revocationTime; // The time when the attestation was revoked (Unix timestamp). + bytes32 refUID; // The UID of the related attestation. + address recipient; // The recipient of the attestation. + address attester; // The attester/sender of the attestation. + bool revocable; // Whether the attestation is revocable. + bytes data; // Custom attestation data. } /// @notice A helper function to work with unchecked iterators in loops. function uncheckedInc(uint256 i) pure returns (uint256 j) { - unchecked { - j = i + 1; - } -} \ No newline at end of file + unchecked { + j = i + 1; + } +} diff --git a/src/interfaces/IResolver.sol b/src/interfaces/IResolver.sol index e219642..035be5f 100644 --- a/src/interfaces/IResolver.sol +++ b/src/interfaces/IResolver.sol @@ -41,6 +41,12 @@ interface IResolver { /// @return Whether the attestation can be revoked. function revoke(Attestation calldata attestation) external payable returns (bool); + /// @notice This function will retrieve all titles allowed in the resolver. + /// It was designed to aid the frontend in displaying the current badges available. + /// NOTE: Only the badges marked as valid will be returned. + /// @return An array of all attestation titles. + function getAllAttestationTitles() external view returns (string[] memory); + /// @dev Sets the attestation for a given title that will be attested. /// When creating attestions, the title must match to the desired configuration saved /// on the resolver. diff --git a/src/resolver/Resolver.sol b/src/resolver/Resolver.sol index 40ba319..05338e3 100644 --- a/src/resolver/Resolver.sol +++ b/src/resolver/Resolver.sol @@ -42,6 +42,9 @@ contract Resolver is IResolver, AccessControl { // Maps schemas ID and role ID to action mapping(bytes32 => Action) private _allowedSchemas; + // Maps all attestation titles (badge titles) to be retrieved by the frontend + string[] private _attestationTitles; + /// @dev Creates a new resolver. /// @param eas The address of the global EAS contract. constructor(IEAS eas) { @@ -229,9 +232,31 @@ contract Resolver is IResolver, AccessControl { return true; } + /// @inheritdoc IResolver + function getAllAttestationTitles() public view returns (string[] memory) { + string[] memory titles = new string[](_attestationTitles.length); + uint256 j = 0; + for (uint256 i = 0; i < _attestationTitles.length; ) { + if (_allowedAttestationTitles[keccak256(abi.encode(_attestationTitles[i]))]) { + titles[j] = _attestationTitles[i]; + assembly { + j := add(j, 1) + } + } + assembly { + i := add(i, 1) + } + } + assembly { + mstore(titles, j) + } + return titles; + } + /// @inheritdoc IResolver function setAttestationTitle(string memory title, bool isValid) public onlyRole(MANAGER_ROLE) { _allowedAttestationTitles[keccak256(abi.encode(title))] = isValid; + if (isValid) _attestationTitles.push(title); } /// @inheritdoc IResolver diff --git a/test/Resolver.t.sol b/test/Resolver.t.sol index 160297f..6429f41 100644 --- a/test/Resolver.t.sol +++ b/test/Resolver.t.sol @@ -27,6 +27,21 @@ contract ResolverTest is Test { resolver = new Resolver(eas); } + function test_access_control_all_badge_titles() public { + string[] memory registeredTitles = test_access_control_add_attest_title(); + string[] memory allTitles = resolver.getAllAttestationTitles(); + assert(allTitles.length == registeredTitles.length); + for (uint256 i = 0; i < allTitles.length; i++) { + assert(keccak256(abi.encode(allTitles[i])) == keccak256(abi.encode(registeredTitles[i]))); + } + resolver.setAttestationTitle(registeredTitles[0], false); + allTitles = resolver.getAllAttestationTitles(); + assert(allTitles.length == registeredTitles.length - 1); + assert(keccak256(abi.encode(allTitles[0])) != keccak256(abi.encode(registeredTitles[0]))); + assert(keccak256(abi.encode(allTitles[0])) == keccak256(abi.encode(registeredTitles[1]))); + assert(keccak256(abi.encode(allTitles[1])) == keccak256(abi.encode(registeredTitles[2]))); + } + function test_access_control_add_attest_title() public returns (string[] memory) { string[] memory titles = new string[](3); titles[0] = "Changed My Mind"; @@ -145,8 +160,6 @@ contract ResolverTest is Test { assert(!hasRole(ROOT_ROLE, deployer)); } - function test_return_all_badge_titles() public {} - function hasRole(bytes32 role, address account) public view returns (bool) { return IAccessControl(address(resolver)).hasRole(role, account); }