Skip to content

Commit

Permalink
feat: WIP store score attestation in resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-schultz committed Nov 22, 2023
1 parent c9d2576 commit fb33a33
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 36 deletions.
67 changes: 67 additions & 0 deletions contracts/GitcoinResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { GitcoinAttester } from "./GitcoinAttester.sol";

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

import "hardhat/console.sol";

/**
* @title GitcoinResolver
* @notice This contract is used to as a resolver contract for EAS schemas, and it will track the last attestation issued for a given recipient.
Expand Down Expand Up @@ -43,6 +45,30 @@ contract GitcoinResolver is
// List of addresses allowed to write to this contract
mapping(address => bool) public allowlist;

// Score data for querying
struct Score {
uint32 score; // compacted uint value 4 decimal places
uint64 issuanceDate; // For checking the age of the stamp, without loading the attestation
uint64 expirationDate; // This makes sense because we want to make sure the stamp is not expired, and also do not want to load the attestation
uint32 scorerId; // would we need this ???
}

/**
* @dev A struct representing the passport score for an ETH address.
*/
// TODO: use same type from IGitcoinPassportDecoder ?
struct ScoreAttestation {
uint256 score;
uint32 scorerID;
uint8 decimals;
}

// Mapping of addresses to scores
mapping(address => Score) public scores;

// Mapping of active passport score schemas - used when storing scores to state
mapping(bytes32 => bool) public scoreSchemas;

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

/**
* @dev Set supported score schemas.
* @param schema The score schema uid
*/
function setScoreSchema(bytes32 schema) external onlyOwner {
scoreSchemas[schema] = true;
}

/**
* @dev Unset supported score schemas.
* @param schema The score schema uid
*/
function unsetScoreSchema(bytes32 schema) external onlyOwner {
scoreSchemas[schema] = false;
}

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

Expand All @@ -108,6 +150,10 @@ contract GitcoinResolver is
function attest(
Attestation calldata attestation
) external payable whenNotPaused onlyAllowlisted returns (bool) {
if (scoreSchemas[attestation.schema]) {
_setScore(attestation);
}

return _attest(attestation);
}

Expand All @@ -122,6 +168,27 @@ contract GitcoinResolver is
return true;
}

/**
* @dev Stores Score data in state.
* @param attestation The new attestation.
*/
function _setScore(Attestation calldata attestation) private {
// Decode the score attestion output
(uint256 score, uint32 scorerId, ) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);

// TODO: save based on schema scores[schema][attestation.recipient]
scores[attestation.recipient] = Score(
// TODO: correct conversion to uint32
uint32(score),
attestation.time,
attestation.expirationTime,
scorerId
);
}

/**
* @dev Processes multiple attestations and verifies whether they are valid.
* @param attestations The new attestations.
Expand Down
4 changes: 2 additions & 2 deletions contracts/IGitcoinPassportDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ struct Credential {
*/
struct Score {
uint256 score;
uint256 scorerID;
uint256 decimals;
uint32 scorerID;
uint8 decimals;
}

/**
Expand Down
113 changes: 80 additions & 33 deletions test/GitcoinResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,43 @@ import {
NO_EXPIRATION,
} from "@ethereum-attestation-service/eas-sdk";
import { GitcoinAttester, GitcoinResolver } from "../typechain-types";
import { encodedData } from "./helpers/mockAttestations";
import { easEncodeScore, encodedData } from "./helpers/mockAttestations";
import { SCHEMA_REGISTRY_ABI } from "./abi/SCHEMA_REGISTRY_ABI";
import { BigNumber } from "@ethersproject/bignumber";

export const schemaRegistryContractAddress =
process.env.SEPOLIA_SCHEMA_REGISTRY_ADDRESS ||
"0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0";

describe("GitcoinResolver", function () {
async function registerSchema(
owner: any,
gitcoinResolverAddress: string,
schema: string
): Promise<string> {
const schemaRegistry = new ethers.Contract(
schemaRegistryContractAddress,
SCHEMA_REGISTRY_ABI,
owner
);

const revocable = true;

const transaction = await schemaRegistry.register(
schema,
gitcoinResolverAddress,
revocable
);

const registerTransactionReceipt = await transaction.wait();

const registerEvent = registerTransactionReceipt.logs.filter((log: any) => {
return log.fragment.name == "Registered";
});

return registerEvent[0].args[0];
}

describe.only("GitcoinResolver", function () {
let owner: any,
iamAccount: any,
recipient: any,
Expand Down Expand Up @@ -42,7 +71,7 @@ describe("GitcoinResolver", function () {
);
gitcoinAttester = await GitcoinAttester.deploy();
await gitcoinAttester.connect(owner).initialize();
const gitcoinAttesterAddress = await gitcoinAttester.getAddress();
this.gitcoinAttesterAddress = await gitcoinAttester.getAddress();

// Initialize the sdk with the address of the EAS Schema contract address
await gitcoinAttester.setEASAddress(mockEas);
Expand All @@ -54,45 +83,34 @@ describe("GitcoinResolver", function () {
gitcoinResolver = await GitcoinResolver.deploy();
await gitcoinResolver
.connect(owner)
.initialize(mockEas.address, gitcoinAttesterAddress);
.initialize(mockEas.address, this.gitcoinAttesterAddress);

const gitcoinResolverAddress = await gitcoinResolver.getAddress();

this.uid = ethers.keccak256(ethers.toUtf8Bytes("test"));

const schemaRegistry = new ethers.Contract(
schemaRegistryContractAddress,
SCHEMA_REGISTRY_ABI,
owner
this.testSchemaUID = await registerSchema(
owner,
gitcoinResolverAddress,
"uint256 eventId, uint8 voteIndex"
);

const schema = "uint256 eventId, uint8 voteIndex";
const resolverAddress = gitcoinResolverAddress;
const revocable = true;

const transaction = await schemaRegistry.register(
schema,
resolverAddress,
revocable
this.scoreSchemaId = await registerSchema(
owner,
gitcoinResolverAddress,
"uint256 score, uint8 scorer_id, uint8 score_decimals"
);

const registerTransactionReceipt = await transaction.wait();

const registerEvent = registerTransactionReceipt.logs.filter((log: any) => {
return log.fragment.name == "Registered";
});

this.schemaUID = registerEvent[0].args[0];
await gitcoinResolver.connect(owner).setScoreSchema(this.scoreSchemaId);

this.validAttestation = {
uid: this.uid,
schema: this.schemaUID,
schema: this.testSchemaUID,
time: NO_EXPIRATION,
expirationTime: NO_EXPIRATION,
revocationTime: NO_EXPIRATION,
refUID: ZERO_BYTES32,
recipient: recipient.address,
attester: gitcoinAttesterAddress,
attester: this.gitcoinAttesterAddress,
revocable: true,
data: encodedData,
};
Expand All @@ -104,7 +122,7 @@ describe("GitcoinResolver", function () {

const attestationUID = await gitcoinResolver.userAttestations(
recipient.address,
this.schemaUID
this.testSchemaUID
);

expect(attestationUID).to.equal(this.uid);
Expand All @@ -120,7 +138,7 @@ describe("GitcoinResolver", function () {

const attestationUID = await gitcoinResolver.userAttestations(
recipient.address,
this.schemaUID
this.testSchemaUID
);

expect(attestationUID).to.equal(this.uid);
Expand All @@ -136,7 +154,7 @@ describe("GitcoinResolver", function () {
const uid = ethers.keccak256(ethers.toUtf8Bytes("test"));
const attestation = {
uid,
schema: this.schemaUID,
schema: this.testSchemaUID,
time: NO_EXPIRATION,
expirationTime: NO_EXPIRATION,
revocationTime: NO_EXPIRATION,
Expand All @@ -153,6 +171,35 @@ describe("GitcoinResolver", function () {
});
});

describe.only("Saving Scores", function () {
it("should save a score", async function () {
const bnScore = BigNumber.from("28287000000000000000");

const scoreAttestation = easEncodeScore({
score: bnScore,
scorer_id: 1,
score_decimals: 0,
});
const uid = ethers.keccak256(ethers.toUtf8Bytes("test"));
const attestation = {
uid,
schema: this.scoreSchemaId,
time: NO_EXPIRATION,
expirationTime: NO_EXPIRATION,
revocationTime: NO_EXPIRATION,
refUID: ZERO_BYTES32,
recipient: recipient.address,
attester: this.gitcoinAttesterAddress,
revocable: true,
data: scoreAttestation,
};

await gitcoinResolver.connect(mockEas).attest(attestation);

const score = await gitcoinResolver.scores(recipient.address);
});
});

describe("Revocations", function () {
it("should make 1 revocation", async function () {
// Make an attestation
Expand All @@ -176,7 +223,7 @@ describe("GitcoinResolver", function () {

let attestationUID = await gitcoinResolver.userAttestations(
recipient.address,
this.schemaUID
this.testSchemaUID
);

expect(attestationUID).to.equal(ethers.ZeroHash);
Expand All @@ -185,7 +232,7 @@ describe("GitcoinResolver", function () {
it("should allow a eas to revoke a user's attestation", async function () {
const validAttestation = {
uid: this.uid,
schema: this.schemaUID,
schema: this.testSchemaUID,
time: NO_EXPIRATION,
expirationTime: NO_EXPIRATION,
revocationTime: NO_EXPIRATION,
Expand All @@ -202,15 +249,15 @@ describe("GitcoinResolver", function () {

let attestationUID = await gitcoinResolver.userAttestations(
recipient.address,
this.schemaUID
this.testSchemaUID
);
expect(attestationUID).to.equal(ethers.ZeroHash);
});

it("should not allow non-EAS to revoke a user's attestations", async function () {
const validAttestation = {
uid: this.uid,
schema: this.schemaUID,
schema: this.testSchemaUID,
time: NO_EXPIRATION,
expirationTime: NO_EXPIRATION,
revocationTime: NO_EXPIRATION,
Expand Down
3 changes: 2 additions & 1 deletion test/helpers/mockAttestations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SchemaEncoder,
ZERO_BYTES32,
NO_EXPIRATION,
SchemaValue,
} from "@ethereum-attestation-service/eas-sdk";

export type Stamp = {
Expand All @@ -11,7 +12,7 @@ export type Stamp = {
};

export type Score = {
score: number;
score: SchemaValue;
scorer_id: number;
score_decimals: number;
};
Expand Down

0 comments on commit fb33a33

Please sign in to comment.