Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(contracts): supporting community specific scores #65

Merged
merged 3 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
430 changes: 430 additions & 0 deletions .openzeppelin/unknown-360.json

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions contracts/GitcoinPassportDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,30 @@ contract GitcoinPassportDecoder is
return score;
}

/**
* @dev Retrieves the user's Score attestation for a given community via the GitcoinResolver and returns it as a 4 digit number
* @param user The ETH address of the recipient
*/
function getScore(
uint32 communityId,
address user
) public view returns (uint256) {
IGitcoinResolver.CachedScore memory cachedScore = gitcoinResolver
.getCachedScore(communityId, user);

if (cachedScore.time != 0) {
// Check for expiration time
if (_isCachedScoreExpired(cachedScore)) {
revert AttestationExpired(cachedScore.time);
}

// Return the score value
return cachedScore.score;
} else {
revert AttestationNotFound();
}
}

/**
* @dev Determines if a user is a human based on their score being above a certain threshold and valid within the max score age
* @param user The ETH address of the recipient
Expand Down
77 changes: 55 additions & 22 deletions contracts/GitcoinResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ contract GitcoinResolver is
// Mapping of active passport score schemas - used when storing scores to state
bytes32 public scoreSchema;

uint32 public defaultCommunityId;

// Mapping of communityId => address => score
mapping(uint32 => mapping(address => CachedScore)) public communityScores;

/**
* @dev Creates a new resolver.
* @notice Initializer function responsible for setting up the contract's initial state.
Expand Down Expand Up @@ -96,6 +101,10 @@ contract GitcoinResolver is
_unpause();
}

function setDefaultCommunityId(uint32 communityId) external onlyOwner {
defaultCommunityId = communityId;
}

/**
* @dev Set supported score schemas.
* @param _schema The score schema uid
Expand Down Expand Up @@ -133,11 +142,11 @@ contract GitcoinResolver is
revert InvalidAttester();
}

userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;

if (scoreSchema == attestation.schema) {
_setScore(attestation);
} else {
userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;
}
return true;
}
Expand All @@ -148,7 +157,7 @@ contract GitcoinResolver is
*/
function _setScore(Attestation calldata attestation) private {
// Decode the score attestion output
(uint256 score, , uint8 digits) = abi.decode(
(uint256 score, uint32 communityId, uint8 digits) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);
Expand All @@ -159,33 +168,59 @@ contract GitcoinResolver is
score *= 10 ** (4 - digits);
}

scores[attestation.recipient] = CachedScore(
CachedScore memory cachedScore = CachedScore(
uint32(score),
attestation.time,
attestation.expirationTime
);

if (communityId == defaultCommunityId || defaultCommunityId == 0) {
scores[attestation.recipient] = cachedScore;
userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;
} else {
communityScores[communityId][attestation.recipient] = cachedScore;
}
}

/**
* @dev Removes the score data from the state for the specified recipient.
* @param recipient The recipient of the score which needs to be removed.
* @param attestation The attestation to be removed.
*/
function _removeScore(address recipient) private {
delete scores[recipient];
function _removeScore(Attestation calldata attestation) private {
// Decode the score attestion output
(, uint32 communityId, ) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);

if (communityId == defaultCommunityId || defaultCommunityId == 0) {
delete scores[attestation.recipient];
delete userAttestations[attestation.recipient][attestation.schema];
} else {
delete communityScores[communityId][attestation.recipient];
}
}

/**
*
* @param user The ETH address of the recipient
* @return The `CachedScore` for the given ETH address.
* A non-zero value in the `time` (issuance time) indicates that a valid score has been retreived.
*/
/// @inheritdoc IGitcoinResolver
function getCachedScore(
address user
) external view returns (CachedScore memory) {
return scores[user];
}

/// @inheritdoc IGitcoinResolver
function getCachedScore(
uint32 communityId,
address user
) external view returns (CachedScore memory) {
if (communityId == defaultCommunityId || defaultCommunityId == 0) {
return scores[user];
} else {
return communityScores[communityId][user];
}
}

/**
* @dev Processes multiple attestations and verifies whether they are valid.
* @param attestations The new attestations.
Expand Down Expand Up @@ -244,18 +279,16 @@ contract GitcoinResolver is
* @return true indicating if the pre-revocation have been performed and the revocation process should continue
*/
function _revoke(Attestation calldata attestation) internal returns (bool) {
userAttestations[attestation.recipient][attestation.schema] = 0;
_removeScore(attestation.recipient);
if (attestation.schema == scoreSchema) {
_removeScore(attestation);
} else {
userAttestations[attestation.recipient][attestation.schema] = 0;
}

return true;
}

/**
*
* @param user The ETH address of the recipient
* @param schema THE UID of the chema
* @return The attestation UID or 0x0 if not found
*/
/// @inheritdoc IGitcoinResolver
function getUserAttestation(
address user,
bytes32 schema
Expand Down
2 changes: 2 additions & 0 deletions contracts/IGitcoinPassportDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ interface IGitcoinPassportDecoder {

function getScore(address user) external view returns (uint256);

function getScore(uint32 communityId, address user) external view returns (uint256);

function isHuman(address user) external view returns (bool);
}
30 changes: 18 additions & 12 deletions contracts/IGitcoinResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,31 @@ interface IGitcoinResolver {
uint64 expirationTime; // This makes sense because we want to make sure the stamp is not expired, and also do not want to load the attestation
}

/**
*
* @param user The ETH address of the recipient
* @param schema THE UID of the chema
* @return The attestation UID or 0x0 if not found
*/
/// @param user The ETH address of the recipient
/// @param schema THE UID of the chema
/// @return The attestation UID or 0x0 if not found
/// @dev Returns the latest user attestation for a given schema
/// @dev Not supported for community-specific attestations
function getUserAttestation(
address user,
bytes32 schema
) external view returns (bytes32);

/**
*
* @param user The ETH address of the recipient
* @return The `CachedScore` for the given ETH address.
* A non-zero value in the `issuanceDate` indicates that a valid score has been retreived.
*/
/// @notice Get the cached score for a user in a the default community
/// @param user The ETH address of the recipient
/// @return The `CachedScore` for the given ETH address.
/// @dev A non-zero value in the `issuanceDate` indicates that a valid score has been retreived.
function getCachedScore(
address user
) external view returns (CachedScore memory);

/// @notice Get the cached score for a user in a specific community
/// @param communityId The ID of the community
/// @param user The ETH address of the recipient
/// @return The `CachedScore` for the given ETH address.
/// @dev A non-zero value in the `issuanceDate` indicates that a valid score has been retreived.
function getCachedScore(
uint32 communityId,
address user
) external view returns (CachedScore memory);
}
87 changes: 79 additions & 8 deletions contracts/mocks/GitcoinResolverUpdate.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

// SPDX-License-Identifier: GPL
pragma solidity ^0.8.9;

Expand Down Expand Up @@ -31,6 +32,8 @@ contract GitcoinResolverUpdate is
error NotAllowlisted();
error InvalidAttester();

event ScoreSchemaSet(bytes32 schema);

// Mapping of addresses to schemas to an attestation UID
mapping(address => mapping(bytes32 => bytes32)) public userAttestations;

Expand All @@ -49,7 +52,12 @@ contract GitcoinResolverUpdate is
// Mapping of active passport score schemas - used when storing scores to state
bytes32 public scoreSchema;

uint256 public aNewPublicVariable;
uint32 public defaultCommunityId;

// Mapping of communityId => address => score
mapping(uint32 => mapping(address => CachedScore)) public communityScores;

uint256 aNewVariable;

/**
* @dev Creates a new resolver.
Expand Down Expand Up @@ -96,6 +104,19 @@ contract GitcoinResolverUpdate is
_unpause();
}

function setDefaultCommunityId(uint32 communityId) external onlyOwner {
defaultCommunityId = communityId;
}

/**
* @dev Set supported score schemas.
* @param _schema The score schema uid
*/
function setScoreSchema(bytes32 _schema) external onlyOwner {
scoreSchema = _schema;
emit ScoreSchemaSet(_schema);
}

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}

Expand Down Expand Up @@ -124,9 +145,12 @@ contract GitcoinResolverUpdate is
revert InvalidAttester();
}

userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;

if (scoreSchema == attestation.schema) {
_setScore(attestation);
} else {
userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;
}
return true;
}

Expand All @@ -136,7 +160,7 @@ contract GitcoinResolverUpdate is
*/
function _setScore(Attestation calldata attestation) private {
// Decode the score attestion output
(uint256 score, , uint8 digits) = abi.decode(
(uint256 score, uint32 communityId, uint8 digits) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);
Expand All @@ -147,22 +171,59 @@ contract GitcoinResolverUpdate is
score *= 10 ** (4 - digits);
}

scores[attestation.recipient] = CachedScore(
CachedScore memory cachedScore = CachedScore(
uint32(score),
attestation.time,
attestation.expirationTime
);

if (communityId == defaultCommunityId) {
scores[attestation.recipient] = cachedScore;
userAttestations[attestation.recipient][attestation.schema] = attestation
.uid;
} else {
communityScores[communityId][attestation.recipient] = cachedScore;
}
}

/**
* @dev Returns the cached score for a given address.
* @dev Removes the score data from the state for the specified recipient.
* @param attestation The attestation to be removed.
*/
function _removeScore(Attestation calldata attestation) private {
// Decode the score attestion output
(, uint32 communityId, ) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);

if (communityId == defaultCommunityId) {
delete scores[attestation.recipient];
delete userAttestations[attestation.recipient][attestation.schema];
} else {
delete communityScores[communityId][attestation.recipient];
}
}

/// @inheritdoc IGitcoinResolver
function getCachedScore(
address user
) external view returns (CachedScore memory) {
return scores[user];
}

/// @inheritdoc IGitcoinResolver
function getCachedScore(
uint32 communityId,
address user
) external view returns (CachedScore memory) {
if (communityId == defaultCommunityId) {
return scores[user];
} else {
return communityScores[communityId][user];
}
}

/**
* @dev Processes multiple attestations and verifies whether they are valid.
* @param attestations The new attestations.
Expand Down Expand Up @@ -215,12 +276,22 @@ contract GitcoinResolverUpdate is
return true;
}

/**
* @dev Processes an revocation request
* @param attestation The new attestation request.
* @return true indicating if the pre-revocation have been performed and the revocation process should continue
*/
function _revoke(Attestation calldata attestation) internal returns (bool) {
userAttestations[attestation.recipient][attestation.schema] = 0;
if (attestation.schema == scoreSchema) {
_removeScore(attestation);
} else {
userAttestations[attestation.recipient][attestation.schema] = 0;
}

return true;
}

/// @inheritdoc IGitcoinResolver
function getUserAttestation(
address user,
bytes32 schema
Expand Down
7 changes: 7 additions & 0 deletions contracts/mocks/MockResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ contract MockResolver is IGitcoinResolver, ISchemaResolver {
return scores[user];
}

function getCachedScore(
uint32 communityId,
address user
) external view returns (CachedScore memory) {
return scores[user];
}

/**
* @dev Processes multiple attestations and verifies whether they are valid.
* @param attestations The new attestations.
Expand Down
Loading
Loading