Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/CRISP/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/CRISP/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"deploy": "gh-pages -d dist"
},
"dependencies": {
"@crisp-e3/sdk": "0.5.9",
"@crisp-e3/sdk": "0.5.10",
"@emotion/babel-plugin": "^11.11.0",
"@emotion/react": "^11.11.4",
"@phosphor-icons/react": "^2.1.4",
Expand Down
5 changes: 5 additions & 0 deletions examples/CRISP/client/src/model/vote.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import { CreditMode } from '@crisp-e3/sdk'

export interface VotingRound {
round_id: number
pk_bytes: number[]
Expand Down Expand Up @@ -54,6 +56,9 @@ export interface VoteStateLite {

committee_public_key: number[]
emojis: [string, string]

credit_mode: CreditMode
credits?: number
}

export type Vote = bigint[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ import { HonkVerifier } from "./CRISPVerifier.sol";
contract CRISPProgram is IE3Program, Ownable {
using InternalLazyIMT for LazyIMTData;

/// @notice Enum to represent credit modes
enum CreditMode {
/// @notice Everyone has constant credits
CONSTANT,
/// @notice Credits are custom (can be based on token balance, etc)
CUSTOM
}

/// @notice Struct to store all data related to a voting round
struct RoundData {
uint256 merkleRoot;
bytes32 paramsHash;
mapping(address slot => uint40 index) voteSlots;
LazyIMTData votes;
uint256 numOptions;
CreditMode creditMode;
}

// Constants
Expand Down Expand Up @@ -121,10 +130,13 @@ contract CRISPProgram is IE3Program, Ownable {
if (e3Data[e3Id].paramsHash != bytes32(0)) revert E3AlreadyInitialized();

// decode custom params to get the number of options
(, , uint256 numOptions) = abi.decode(customParams, (address, uint256, uint256));
(, , uint256 numOptions, CreditMode creditMode, ) = abi.decode(customParams, (address, uint256, uint256, CreditMode, uint256));
if (numOptions < 2) revert InvalidNumOptions();

// we need to know the number of options for decoding the tally
e3Data[e3Id].numOptions = numOptions;
// we want to save the credit mode so it can be verified on chain by everyone
e3Data[e3Id].creditMode = creditMode;
Comment thread
ctrlc03 marked this conversation as resolved.

e3Data[e3Id].paramsHash = keccak256(e3ProgramParams);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ contract MockEnclave {
encryptionSchemeId: bytes32(0),
e3Program: IE3Program(program),
e3ProgramParams: bytes(""),
customParams: abi.encode(address(0), nextE3Id, 2),
customParams: abi.encode(address(0), nextE3Id, 2, 0, 0),
decryptionVerifier: IDecryptionVerifier(address(0)),
committeePublicKey: committeePublicKey,
ciphertextOutput: bytes32(0),
plaintextOutput: plaintextOutput,
requester: address(0)
});

IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2));
IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2, 0, 0));

nextE3Id++;
}
Expand All @@ -61,7 +61,7 @@ contract MockEnclave {
encryptionSchemeId: bytes32(0),
e3Program: IE3Program(address(0)),
e3ProgramParams: bytes(""),
customParams: abi.encode(address(0), 0, 2),
customParams: abi.encode(address(0), 0, 2, 0, 0),
decryptionVerifier: IDecryptionVerifier(address(0)),
committeePublicKey: committeePublicKey,
ciphertextOutput: bytes32(0),
Expand Down
2 changes: 1 addition & 1 deletion examples/CRISP/packages/crisp-contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@crisp-e3/contracts",
"version": "0.5.9",
"version": "0.5.10",
"type": "module",
"files": [
"contracts",
Expand Down
2 changes: 1 addition & 1 deletion examples/CRISP/packages/crisp-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@crisp-e3/sdk",
"version": "0.5.9",
"version": "0.5.10",
"type": "module",
"author": {
"name": "gnosisguild",
Expand Down
1 change: 1 addition & 0 deletions examples/CRISP/packages/crisp-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export {
export { CrispSDK } from './sdk'

export type { RoundDetails, RoundDetailsResponse, TokenDetails, Vote, MaskVoteProofInputs, VoteProofInputs } from './types'
export { CreditMode } from './types'
10 changes: 10 additions & 0 deletions examples/CRISP/packages/crisp-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,13 @@ export type VoteProofRequest = {
messageHash: `0x${string}`
slotAddress: string
}

/**
* Enum representing the credit mode for a round, which can be either constant or custom.
* In constant mode, all voters receive the same amount of credits, while in custom mode,
* the credits can vary based on certain criteria (e.g., voter balance).
*/
export enum CreditMode {
CONSTANT = 0,
CUSTOM = 1,
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion examples/CRISP/packages/crisp-zk-inputs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@crisp-e3/zk-inputs",
"type": "module",
"description": "Core logic to pre-compute CRISP ZK inputs (WASM/JavaScript bindings).",
"version": "0.5.9",
"version": "0.5.10",
"license": "LGPL-3.0-only",
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions examples/CRISP/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dialoguer = { version = "=0.11.0", features = ["fuzzy-select"] }
# Serialization and deserialization
bincode.workspace = true
serde.workspace = true
serde_repr = "0.1.20"
serde_json.workspace = true
eyre.workspace = true
derivative.workspace = true
Expand Down
7 changes: 6 additions & 1 deletion examples/CRISP/server/src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ pub async fn initialize_crisp_round(

let token_address: Address = token_address.parse()?;
let balance_threshold = U256::from_str_radix(&balance_threshold, 10)?;
// We default to two options for the main CRISP app
let num_options = U256::from(2);
// The credit mode is constant for the CRISP app (everyone gets the same credits)
let credit_mode = U256::from(0);
// everyone gets 1 credit
let credits = U256::from(1);

// Serialize the custom parameters to bytes.
let custom_params_bytes = Bytes::from((token_address, balance_threshold, num_options).abi_encode());
let custom_params_bytes = Bytes::from((token_address, balance_threshold, num_options, credit_mode, credits).abi_encode());

let threshold: [u32; 2] = [CONFIG.e3_threshold_min, CONFIG.e3_threshold_max];
let mut current_timestamp = get_current_timestamp().await?;
Expand Down
83 changes: 60 additions & 23 deletions examples/CRISP/server/src/server/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use crate::server::token_holders::{get_mock_token_holders, EtherscanClient};
use crate::server::{
models::{CurrentRound, CustomParams},
models::{CreditMode, CurrentRound, CustomParams},
program_server_request::run_compute,
repo::{CrispE3Repository, CurrentRoundRepository},
token_holders::{build_tree, compute_token_holder_hashes},
Expand Down Expand Up @@ -69,15 +69,31 @@ pub async fn register_e3_requested(
// Convert custom params bytes back to token address and balance threshold.

// Use sol_data types instead of primitives
type CustomParamsTuple = (sol_data::Address, sol_data::Uint<256>, sol_data::Uint<256>);
type CustomParamsTuple = (sol_data::Address, sol_data::Uint<256>, sol_data::Uint<256>, sol_data::Uint<256>, sol_data::Uint<256>);

let decoded = <CustomParamsTuple as SolType>::abi_decode(&event.e3.customParams)
.with_context(|| "Failed to decode custom params from E3 event")?;

let credit_mode = CreditMode::try_from(decoded.3.to::<u64>())?;
let credits = match credit_mode {
CreditMode::Constant => {
info!("[e3_id={}] Credit mode: Constant", e3_id);
Some(decoded.4.to_string())
}
CreditMode::Custom => {
info!("[e3_id={}] Credit mode: Custom", e3_id);
None
}
};

let credits_clone = credits.clone();

let custom_params = CustomParams {
token_address: decoded.0.to_string(),
balance_threshold: decoded.1.to_string(),
num_options: decoded.2.to_string(),
credit_mode,
credits,
};

let balance_threshold =
Expand All @@ -88,10 +104,6 @@ pub async fn register_e3_requested(
.parse()
.with_context(|| "Invalid token address")?;

// save the e3 details
repo.initialize_round(custom_params.token_address, custom_params.balance_threshold, custom_params.num_options, e3.requester.to_string())
.await?;

// Get token holders from Etherscan API or mocked data.
let token_holders = if matches!(CONFIG.chain_id, 31337 | 1337) {
info!(
Expand All @@ -108,23 +120,44 @@ pub async fn register_e3_requested(

let etherscan_client =
EtherscanClient::new(CONFIG.etherscan_api_key.clone(), CONFIG.chain_id);
etherscan_client
.get_token_holders_with_voting_power(
token_address,
event.e3.requestBlock.to::<u64>(),
&CONFIG.http_rpc_url,
U256::from_str_radix(&balance_threshold.to_string(), 10).map_err(
|e| {
eyre::eyre!(
"[e3_id={}] Failed to convert balance threshold to U256: {}",
e3_id,
e
)
},
)?,
)
.await
.map_err(|e| eyre::eyre!("Etherscan error: {}", e))?

match custom_params.credit_mode {
CreditMode::Constant => {
let credits_str = credits_clone.expect("credits must be set for Constant mode");
let credits_u256: alloy_primitives::Uint<256, 4> = U256::from_str_radix(&credits_str, 10)
.map_err(|e| eyre::eyre!("Failed to parse credits: {}", e))?;

etherscan_client
.get_token_holders_with_constant_balance(
token_address,
// the block is the one before the request
event.e3.requestBlock.to::<u64>() - 1u64,
credits_u256
)
.await
.map_err(|e| eyre::eyre!("Etherscan error: {}", e))?
Comment thread
ctrlc03 marked this conversation as resolved.
}
CreditMode::Custom => {
etherscan_client
.get_token_holders_with_voting_power(
token_address,
// the block is the one before the request
event.e3.requestBlock.to::<u64>() - 1u64,
&CONFIG.http_rpc_url,
U256::from_str_radix(&balance_threshold.to_string(), 10).map_err(
|e| {
eyre::eyre!(
"[e3_id={}] Failed to convert balance threshold to U256: {}",
e3_id,
e
)
},
)?,
)
.await
.map_err(|e| eyre::eyre!("Etherscan error: {}", e))?
}
}
};

if token_holders.is_empty() {
Expand All @@ -136,6 +169,10 @@ pub async fn register_e3_requested(
.into());
}

// save the e3 details
repo.initialize_round(custom_params, e3.requester.to_string())
.await?;

// Store eligible addresses in the repository.
repo.set_eligible_addresses(token_holders.clone())
.await?;
Expand Down
30 changes: 30 additions & 0 deletions examples/CRISP/server/src/server/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use anyhow::Result;
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
use serde_repr::{Serialize_repr, Deserialize_repr};

#[derive(Derivative, Deserialize, Serialize)]
#[derivative(Debug)]
Expand Down Expand Up @@ -144,6 +145,8 @@ pub struct CustomParams {
pub token_address: String,
pub balance_threshold: String,
pub num_options: String,
pub credit_mode: CreditMode,
pub credits: Option<String>,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -187,6 +190,9 @@ pub struct E3StateLite {
pub num_options: String,

pub requester: String,

pub credit_mode: CreditMode,
pub credits: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -242,6 +248,8 @@ pub struct E3Crisp {
pub ciphertext_inputs: Vec<(Vec<u8>, u64)>,
pub requester: String,
pub num_options: String,
pub credit_mode: CreditMode,
pub credits: Option<String>,
}

impl From<E3> for WebResultRequest {
Expand All @@ -266,3 +274,25 @@ pub struct TokenHolder {
pub address: String,
pub balance: String,
}

/// Defines the mode of credit assignment for voters.
/// - `Constant`: All voters receive the same credit regardless of their token balance.
/// - `Custom`: Voters receive credit proportional to their token balance, with a specified threshold.
#[derive(Debug, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum CreditMode {
Constant = 0,
Custom = 1,
}

impl TryFrom<u64> for CreditMode {
type Error = eyre::Error;

fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
0 => Ok(CreditMode::Constant),
1 => Ok(CreditMode::Custom),
_ => Err(eyre::eyre!("Unknown credit mode: {}", value)),
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading