generated from proofoftom/buidler-waffle-typechain-quasar
-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #711 from clrfund/feat/merkle-user-registry
Add merkle and snapshot user registry
- Loading branch information
Showing
44 changed files
with
1,939 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Common clr.fund utility functions used by contracts and vue-app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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;` |
Oops, something went wrong.