Skip to content

Commit

Permalink
feat(contracts): supporting community specific scores (#65)
Browse files Browse the repository at this point in the history
* feat(contracts): supporting community specific scores

* dropped communityUserAttestations

* shape deployment
  • Loading branch information
lucianHymer authored Sep 28, 2024
1 parent f8d3fc8 commit 21feffe
Show file tree
Hide file tree
Showing 10 changed files with 824 additions and 42 deletions.
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

0 comments on commit 21feffe

Please sign in to comment.