Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into cohort/clrfund
Browse files Browse the repository at this point in the history
  • Loading branch information
yuetloo committed Aug 29, 2023
2 parents 5c29125 + d9ed7ce commit 7dd2dcc
Show file tree
Hide file tree
Showing 46 changed files with 1,942 additions and 246 deletions.
2 changes: 2 additions & 0 deletions common/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
node_modules
1 change: 1 addition & 0 deletions common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Common clr.fund utility functions used by contracts and vue-app
29 changes: 29 additions & 0 deletions common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@clrfund/common",
"version": "0.0.1",
"description": "Common utility functions used by clrfund scripts and app",
"main": "src/index",
"scripts": {
"build": "tsc",
"lint": "eslint 'src/**/*.ts'",
"clean": "rm -rf build"
},
"license": "GPL-3.0",
"devDependencies": {
"eslint": "^8.31.0",
"typescript": "^4.9.3"
},
"dependencies": {
"@openzeppelin/merkle-tree": "^1.0.5",
"ethers": "^5.7.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/clrfund/monorepo.git"
},
"author": "",
"bugs": {
"url": "https://github.com/clrfund/monorepo/issues"
},
"homepage": "https://github.com/clrfund/monorepo#readme"
}
20 changes: 20 additions & 0 deletions common/src/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { providers, utils } from 'ethers'

export interface Block {
blockNumber: number
hash: string
stateRoot: string
}

/*
* get the block stateRoot using eth_getBlockByHash
*/
export async function getBlock(
blockNumber: number,
provider: providers.JsonRpcProvider
): Promise<Block> {
const blockNumberHex = utils.hexValue(blockNumber)
const blockParams = [blockNumberHex, false]
const rawBlock = await provider.send('eth_getBlockByNumber', blockParams)
return { blockNumber, hash: rawBlock.hash, stateRoot: rawBlock.stateRoot }
}
4 changes: 4 additions & 0 deletions common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './block'
export * from './proof'
export * from './merkle'
export * from './ipfs'
18 changes: 18 additions & 0 deletions common/src/ipfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { utils } from 'ethers'

const IPFS_BASE_URL = 'https://ipfs.io'

/**
* Get the IPFS content given the IPFS hash
* @param hash The IPFS hash
* @param gatewayUrl The IPFS gateway url
* @returns The IPFS content
*/
export async function getIpfsContent(
hash: string,
gatewayUrl = IPFS_BASE_URL
): Promise<any> {
const url = `${gatewayUrl}/ipfs/${hash}`
const result = utils.fetchJson(url)
return result
}
37 changes: 37 additions & 0 deletions common/src/merkle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { StandardMerkleTree } from '@openzeppelin/merkle-tree'

/**
* Load users into a merkle tree
* @param users Users to load
* @returns user merkle tree
*/
export function loadUserMerkleTree(
users: string[]
): StandardMerkleTree<string[]> {
const tree = StandardMerkleTree.of(
users.map((user) => [user.toLowerCase()]),
['address']
)
return tree
}

/**
* Get the merkle proof for the user
* @param userAccount User wallet address to get the proof for
* @param userMerkleTree The merkle tree containing all approved users
* @returns
*/
export function getUserMerkleProof(
userAccount: string,
userMerkleTree: StandardMerkleTree<string[]>
): string[] | null {
try {
return userMerkleTree.getProof([userAccount.toLowerCase()])
} catch (err) {
console.log('userAccount', userAccount.toLowerCase())
console.log('getUserMerkleProof error', err)
return null
}
}

export { StandardMerkleTree }
85 changes: 85 additions & 0 deletions common/src/proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { utils, providers } from 'ethers'

/**
* RLP encode the proof returned from eth_getProof
* @param proof proof from the eth_getProof
* @returns
*/
export function rlpEncodeProof(proof: string[]) {
const decodedProof = proof.map((node: string) => utils.RLP.decode(node))

return utils.RLP.encode(decodedProof)
}

/**
* The storage key used in eth_getProof and eth_getStorageAt
* @param account Account address
* @param slotIndex Slot index of the balanceOf storage
* @returns storage key used in the eth_getProof params
*/
export function getStorageKey(account: string, slotIndex: number) {
return utils.keccak256(
utils.concat([
utils.hexZeroPad(account, 32),
utils.hexZeroPad(utils.hexValue(slotIndex), 32),
])
)
}

/**
* Get proof from eth_getProof
* @param params Parameter fro eth_getProof
* @returns proof returned from eth_getProof
*/
async function getProof(
params: Array<string | string[]>,
provider: providers.JsonRpcProvider
): Promise<any> {
try {
const proof = await provider.send('eth_getProof', params)
return proof
} catch (err) {
console.error(
'Unable to get proof. Your node may not support eth_getProof. Try a different provider such as Infura',
err
)
throw err
}
}
/**
* Get the storage proof
* @param token Token contract address
* @param blockHash The block hash to get the proof for
* @param provider provider to connect to the node
* @returns proof returned from eth_getProof
*/
export async function getAccountProof(
token: string,
blockHash: string,
provider: providers.JsonRpcProvider
): Promise<any> {
const params = [token, [], blockHash]
return getProof(params, provider)
}

/**
* Get the storage proof
* @param token Token contract address
* @param blockHash The block hash to get the storage proof for
* @param userAccount User account to get the proof for
* @param storageSlotIndex The storage index for the balanceOf storage
* @param provider provider to connect to the node
* @returns proof returned from eth_getProof
*/
export async function getStorageProof(
token: string,
blockHash: string,
userAccount: string,
storageSlotIndex: number,
provider: providers.JsonRpcProvider
): Promise<any> {
const storageKey = getStorageKey(userAccount, storageSlotIndex)

const params = [token, [storageKey], blockHash]
return getProof(params, provider)
}
22 changes: 22 additions & 0 deletions common/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"skipLibCheck": true,
"experimentalDecorators": true,
"alwaysStrict": true,
"noImplicitAny": false,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"strict": true,
"outDir": "./build",
"target": "es2018",
"esModuleInterop": true,
"module": "commonjs",
"declaration": true
},
"exclude": ["node_modules/**"],
"include": ["./src"]
}
2 changes: 1 addition & 1 deletion contracts/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Recipient registry type for local deployment: simple, optimistic
RECIPIENT_REGISTRY_TYPE=optimistic

# Supported values: simple, brightid
# Supported values: simple, brightid, snapshot, merkle
USER_REGISTRY_TYPE=simple
# clr.fund (prod) or CLRFundTest (testing)
BRIGHTID_CONTEXT=clr.fund
Expand Down
78 changes: 78 additions & 0 deletions contracts/contracts/userRegistry/MerkleUserRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.6.12;

import '@openzeppelin/contracts/access/Ownable.sol';

import './IUserRegistry.sol';
import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";


/**
* @dev A merkle user registry add users to the registry based on
* a successful verification against the merkle root set by
* the funding round coordinator.
*/
contract MerkleUserRegistry is Ownable, IUserRegistry {

// verified users grouped by merkleRoot
// merkleRoot -> user -> status
mapping(bytes32 => mapping(address => bool)) private users;

// merkle root
bytes32 public merkleRoot;

// ipfs hash of the merkle tree file
string public merkleHash;

// Events
event UserAdded(address indexed _user, bytes32 indexed merkleRoot);
event MerkleRootChanged(bytes32 indexed root, string ipfsHash);

/**
* @dev Set the merkle root used to verify users
* @param root Merkle root
* @param ipfsHash The IPFS hash of the merkle tree file
*/
function setMerkleRoot(bytes32 root, string calldata ipfsHash) external onlyOwner {
require(root != bytes32(0), 'MerkleUserRegistry: Merkle root is zero');
require(bytes(ipfsHash).length != 0, 'MerkleUserRegistry: Merkle hash is empty string');

merkleRoot = root;
merkleHash = ipfsHash;

emit MerkleRootChanged(root, ipfsHash);
}

/**
* @dev Add verified unique user to the registry.
*/
function addUser(address _user, bytes32[] calldata proof)
external
{
require(merkleRoot != bytes32(0), 'MerkleUserRegistry: Merkle root is not initialized');
require(_user != address(0), 'MerkleUserRegistry: User address is zero');
require(!users[merkleRoot][_user], 'MerkleUserRegistry: User already verified');

// verifies user against the merkle root
bytes32 leaf = keccak256(abi.encodePacked(keccak256(abi.encode(_user))));
bool verified = MerkleProof.verifyCalldata(proof, merkleRoot, leaf);
require(verified, 'MerkleUserRegistry: User is not authorized');

users[merkleRoot][_user] = true;
emit UserAdded(_user, merkleRoot);

}

/**
* @dev Check if the user is verified.
*/
function isVerifiedUser(address _user)
override
external
view
returns (bool)
{
return users[merkleRoot][_user];
}
}
47 changes: 8 additions & 39 deletions contracts/contracts/userRegistry/README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,21 @@
## Description

### BrightIdUserRegistry

This is a contract to register verified users context ids by BrightID node's verification data, and be able to query a user verification.
This contract consist of:

- Set BrightID settings <br />`function setSettings(bytes32 _context, address _verifier) external onlyOwner;`
- Check a user is verified or not <br />`function isVerifiedUser(address _user) override external view returns (bool);`
- Register a user by BrightID node's verification data <br />`function register(bytes32 _context, address[] calldata _addrs, uint _timestamp, uint8 _v, bytes32 _r, bytes32 _s external;`

## Demonstration

> TODO: update the following with a goerli contract
[Demo contract on the Rinkeby](https://rinkeby.etherscan.io/address/0xf99e2173db1f341a947ce9bd7779af2245309f91)
Sample of Registered Data:

```
{
"data": {
"unique": true,
"context": "clr.fund",
"contextIds": [
"0xb1775295f3b250c2849366801149479471fa7362",
"0x9ed6d9086f5ee9edc14dd2caca44d65ee8cabdde",
"0x79af508c9698076bc1c2dfa224f7829e9768b11e"
],
"sig": {
"r": "ec6a9c3e10f238acb757ceea5507cf33366acd05356d513ca80cd1148297d079",
"s": "0e918c709ea7a458f7c95769145f475df94c01f3bc9e9ededf38153aa5b9041b",
"v": 28
},
"timestamp": 1602353670884,
"publicKey": "03ab573225151072be57d4808861e0f706595fb143c71630e188051fe4a6bda594"
}
}
```

You can see the contract settings [here](https://rinkeby.etherscan.io/address/0xf99e2173db1f341a947ce9bd7779af2245309f91#readContract)

You can update the BrightID settings and test register [here](https://rinkeby.etherscan.io/address/0xf99e2173db1f341a947ce9bd7779af2245309f91#writeContract)
### SnapshotUserRegistry

## Deploy contract
This is a contract to register verified users by the proof that the users held the minimum amount of tokens at a given block.

This contract needs two constructor arguments
The main functions:

- `context bytes32` <br /> BrightID context used for verifying users.

- `verifier address` <br /> BrightID verifier address that signs BrightID verifications.

## Points

We can simply use an ERC20 token as authorization for the verifiers to be able have multiple verifiers.
- Set storage root <br />`function setStorageRoot(address tokenAddress, bytes32 stateRoot uint256 slotIndex, bytes memory accountProofRlpBytes) external onlyOwner;`
- Check a user is verified or not <br />`function isVerifiedUser(address _user) override external view returns (bool);`
- Add a user with the proof from eth_getProof <br />`function addUser(address _user, bytes memory storageProofRlpBytes) external;`
Loading

0 comments on commit 7dd2dcc

Please sign in to comment.