Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

Commit

Permalink
feat(evm): add merkletree whitelist strategy (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sekhmet authored Oct 14, 2023
1 parent 3f76db1 commit e48a34d
Show file tree
Hide file tree
Showing 17 changed files with 1,722 additions and 1,108 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@ethersproject/providers": "^5.7.0",
"@ethersproject/solidity": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"@openzeppelin/merkle-tree": "^1.0.5",
"cross-fetch": "^3.1.5",
"micro-starknet": "^0.2.3",
"randombytes": "^2.1.0",
Expand Down
5 changes: 3 additions & 2 deletions src/clients/evm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type IndexedConfig = {
export type StrategyConfig = {
index: number;
address: string;
metadata?: Record<string, any>;
};

export type Propose = {
Expand All @@ -29,7 +30,6 @@ export type Propose = {
strategies: StrategyConfig[];
executionStrategy: AddressConfig;
metadataUri: string;
extraProperties?: Record<string, any>;
};

export type UpdateProposal = {
Expand All @@ -47,7 +47,6 @@ export type Vote = {
proposal: number;
choice: Choice;
metadataUri: string;
extraProperties?: Record<string, any>;
};

export type Call = {
Expand All @@ -70,11 +69,13 @@ export type Strategy = {
call: 'propose' | 'vote',
strategyConfig: StrategyConfig,
signerAddress: string,
metadata: Record<string, any> | null,
data: Propose | Vote
): Promise<string>;
getVotingPower(
strategyAddress: string,
voterAddress: string,
metadata: Record<string, any> | null,
block: number,
params: string,
provider: Provider
Expand Down
1 change: 1 addition & 0 deletions src/strategies/evm/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function createCompStrategy(): Strategy {
async getVotingPower(
strategyAddress: string,
voterAddress: string,
metadata: Record<string, any> | null,
block: number,
params: string,
provider: Provider
Expand Down
12 changes: 9 additions & 3 deletions src/strategies/evm/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import createVanillaStrategy from './vanilla';
import createCompStrategy from './comp';
import createOzVotesStrategy from './ozVotes';
import createWhitelistStrategy from './whitelist';
import createMerkleWhitelist from './merkleWhitelist';
import type { Propose, Vote, StrategyConfig, Strategy } from '../../clients/evm/types';
import type { EvmNetworkConfig } from '../../types';

Expand All @@ -22,7 +22,7 @@ export function getStrategy(address: string, networkConfig: EvmNetworkConfig): S
}

if (strategy.type === 'whitelist') {
return createWhitelistStrategy();
return createMerkleWhitelist();
}

return null;
Expand All @@ -40,7 +40,13 @@ export async function getStrategiesParams(
const strategy = getStrategy(strategyConfig.address, networkConfig);
if (!strategy) throw new Error('Invalid strategy');

return strategy.getParams(call, strategyConfig, signerAddress, data);
return strategy.getParams(
call,
strategyConfig,
signerAddress,
strategyConfig.metadata || null,
data
);
})
);
}
58 changes: 58 additions & 0 deletions src/strategies/evm/merkleWhitelist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AbiCoder } from '@ethersproject/abi';
import { StandardMerkleTree } from '@openzeppelin/merkle-tree';
import { Strategy, StrategyConfig } from '../../clients/evm/types';

function getProofForVoter(tree: StandardMerkleTree<[string, bigint]>, voter: string) {
for (const [i, v] of tree.entries()) {
if ((v[0] as string).toLowerCase() === voter.toLowerCase()) {
return { index: i, proof: tree.getProof(i) };
}
}

return null;
}

export default function createMerkleWhitelist(): Strategy {
return {
type: 'whitelist',
async getParams(
call: 'propose' | 'vote',
strategyConfig: StrategyConfig,
signerAddress: string,
metadata: Record<string, any> | null
): Promise<string> {
const tree = metadata?.tree;

if (!tree) throw new Error('Invalid metadata. Missing tree');

const whitelist: [string, bigint][] = tree.map(entry => [entry.address, entry.votingPower]);
const merkleTree = StandardMerkleTree.of(whitelist, ['address', 'uint96']);
const proof = getProofForVoter(merkleTree, signerAddress);

if (!proof) throw new Error('Signer is not in whitelist');

const abiCoder = new AbiCoder();
return abiCoder.encode(
['bytes32[]', 'tuple(address, uint96)'],
[proof.proof, whitelist[proof.index]]
);
},
async getVotingPower(
strategyAddress: string,
voterAddress: string,
metadata: Record<string, any> | null
): Promise<bigint> {
const tree = metadata?.tree;

if (!tree) throw new Error('Invalid metadata. Missing tree');

const match = tree.find(entry => entry.address.toLowerCase() === voterAddress.toLowerCase());

if (match) {
return BigInt(match.votingPower.toString());
}

return 0n;
}
};
}
1 change: 1 addition & 0 deletions src/strategies/evm/ozVotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function createOzVotesStrategy(): Strategy {
async getVotingPower(
strategyAddress: string,
voterAddress: string,
metadata: Record<string, any> | null,
block: number,
params: string,
provider: Provider
Expand Down
40 changes: 0 additions & 40 deletions src/strategies/evm/whitelist.ts

This file was deleted.

Loading

0 comments on commit e48a34d

Please sign in to comment.