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
1 change: 0 additions & 1 deletion examples/CRISP/scripts/evm_deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ set -euo pipefail

wait-on tcp:8545 && \
(cd ../../packages/enclave-contracts && \
rm -rf deployments/localhost && \
pnpm deploy:mocks --network localhost)
3 changes: 3 additions & 0 deletions examples/CRISP/sdk/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules
.prettierrc
10 changes: 10 additions & 0 deletions examples/CRISP/sdk/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"printWidth": 140,
"useTabs": false,
"tabWidth": 2,
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"trailingComma": "all",
"arrowParens": "always"
}
39 changes: 39 additions & 0 deletions examples/CRISP/sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "crisp-sdk",
"version": "0.0.1",
"type": "module",
"files": [
"dist"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"author": {
"name": "gnosisguild",
"url": "https://github.com/gnosisguild"
},
"homepage": "https://github.com/gnosisguild/enclave",
"scripts": {
"build": "tsc",
"test": "vitest --run",
"prettier:fix": "pnpm prettier --write .",
"prettier:check": "pnpm prettier --check ."
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/chai": "^5.2.2",
"@types/node": "22.7.5",
"chai": "^6.2.0",
"prettier": "^3.2.5",
"vitest": "^1.6.1",
"typescript": "^5.0.0"
},
"packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808"
}
7 changes: 7 additions & 0 deletions examples/CRISP/sdk/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

export const CRISP_SERVER_TOKEN_TREE_ENDPOINT = 'state/token-holders'
7 changes: 7 additions & 0 deletions examples/CRISP/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

export * from "./token";
54 changes: 54 additions & 0 deletions examples/CRISP/sdk/src/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import { CRISP_SERVER_TOKEN_TREE_ENDPOINT } from './constants'

/**
* Get the merkle tree data from the CRISP server
* @param serverUrl - The base URL of the CRISP server
* @param e3Id - The e3Id of the round
*/
export const getTreeData = async (serverUrl: string, e3Id: number) => {
try {
const response = await fetch(`${serverUrl}/${CRISP_SERVER_TOKEN_TREE_ENDPOINT}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ round_id: e3Id }),
})

const hashes = await response.json()

return hashes
} catch (error) {
console.error('Error fetching tree data:', error)
}
}
Comment thread
ctrlc03 marked this conversation as resolved.

/**
* Generate a Merkle proof for a given address to prove inclusion in the voters' list
*/
export const generateMerkleProof = () => {}

/**
* Get the token balance at a specific block for a given address
*/
export const getBalanceAt = () => {}

/**
* Interface representing the details of a specific round
*/
export interface IRoundDetails {
tokenAddress: string
snapshotBlock: string
threshold: string
}

/**
* Get the details of a specific round
*/
export const getRoundDetails = () => {}
18 changes: 18 additions & 0 deletions examples/CRISP/sdk/tests/token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import { describe, expect, it } from 'vitest'

import { getTreeData } from '../src/token'

// @notice To run these tests you will need to have an instance of CRISP running locally
describe('Token data fetching', () => {
const serverUrl = 'http://localhost:4000'
it('should fetch token data from the CRISP server', async () => {
const data = await getTreeData(serverUrl, 0)
expect(data.length).toBeGreaterThan(0)
})
})
Comment thread
ctrlc03 marked this conversation as resolved.
13 changes: 13 additions & 0 deletions examples/CRISP/sdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"outDir": "dist",
"declaration": true
},
"include": ["src/**/*.ts"]
}
4 changes: 3 additions & 1 deletion examples/CRISP/server/src/server/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub async fn register_e3_requested(
info!("E3Requested: {:?}", event);

async move {
repo.initialize_round().await?;

// Convert custom params bytes back to token address and balance threshold.
let custom_params: CustomParams =
serde_json::from_slice(&event.e3.customParams)
Expand Down Expand Up @@ -134,7 +136,7 @@ pub async fn register_e3_activated(

info!("Handling E3 request with id {}", e3_id);
async move {
repo.initialize_round().await?;
repo.start_round().await?;

current_round_repo
.set_current_round(CurrentRound { id: e3_id })
Expand Down
37 changes: 32 additions & 5 deletions examples/CRISP/server/src/server/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use super::{
database::generate_emoji,
models::{CurrentRound, E3Crisp, E3StateLite, WebResultRequest},
};
use chrono::Utc;
use e3_sdk::indexer::{models::E3 as EnclaveE3, DataStore, E3Repository, SharedStore};
use eyre::Result;
use log::info;
Expand Down Expand Up @@ -85,13 +84,38 @@ impl<S: DataStore> CrispE3Repository<S> {
Ok(e3_crisp)
}

pub async fn initialize_round(&mut self) -> Result<()> {
let start_time = Utc::now().timestamp() as u64;
pub async fn start_round(&mut self) -> Result<()> {
let start_time = chrono::Utc::now().timestamp() as u64;
let key = self.crisp_key();

self.store
.modify(&key, |e3_obj: Option<E3Crisp>| {
e3_obj.map(|mut e| {
e.start_time = start_time;
e
})
})
.await
.map_err(|_| eyre::eyre!("Could not update start_time for '{key}'"))?;

self.store
.modify(&key, |e3_obj: Option<E3Crisp>| {
e3_obj.map(|mut e| {
e.status = "Active".to_string();
e
})
})
.await
.map_err(|_| eyre::eyre!("Could not update status for '{key}'"))?;

Ok(())
}

pub async fn initialize_round(&mut self) -> Result<()> {
self.set_crisp(E3Crisp {
has_voted: vec![],
start_time,
status: "Active".to_string(),
start_time: 0u64,
status: "Requested".to_string(),
votes_option_1: 0,
votes_option_2: 0,
emojis: generate_emoji(),
Expand Down Expand Up @@ -236,6 +260,7 @@ impl<S: DataStore> CrispE3Repository<S> {

pub async fn set_token_holder_hashes(&mut self, hashes: Vec<String>) -> Result<()> {
let key = self.crisp_key();

self.store
.modify(&key, |e3_obj: Option<E3Crisp>| {
e3_obj.map(|mut e| {
Expand All @@ -245,11 +270,13 @@ impl<S: DataStore> CrispE3Repository<S> {
})
.await
.map_err(|_| eyre::eyre!("Could not set token_holder_hashes for '{key}'"))?;

Ok(())
}

pub async fn get_token_holder_hashes(&self) -> Result<Vec<String>> {
let e3_crisp = self.get_crisp().await?;

Ok(e3_crisp.token_holder_hashes)
}

Expand Down
56 changes: 44 additions & 12 deletions examples/CRISP/server/src/server/routes/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,50 @@ use crate::server::{
models::{GetRoundRequest, WebhookPayload},
CONFIG,
};
use alloy::primitives::{U256, Bytes};
use actix_web::{web, HttpResponse, Responder};
use alloy::primitives::{Bytes, U256};
use e3_sdk::evm_helpers::contracts::{
EnclaveContract, EnclaveContractFactory, EnclaveWrite, ReadWrite,
};
use log::{error, info};
use e3_sdk::evm_helpers::contracts::{EnclaveContract, EnclaveContractFactory, EnclaveWrite, ReadWrite};

pub fn setup_routes(config: &mut web::ServiceConfig) {
config.service(
web::scope("/state")
.route("/result", web::post().to(get_round_result))
.route("/all", web::get().to(get_all_round_results))
.route("/lite", web::post().to(get_round_state_lite))
// Do we need protection on this endpoint? technically they would need to send a valid proof for it to
// be included on chain
.route("/add-result", web::post().to(handle_program_server_result)),
// Do we need protection on this endpoint? technically they would need to send a valid proof for it to
// be included on chain
.route("/add-result", web::post().to(handle_program_server_result))
// Get the token holders hashes for a given round
.route("/token-holders", web::post().to(get_token_holders_hashes)),
);
Comment thread
ctrlc03 marked this conversation as resolved.
}

/// Webhook callback from program server
///
///
/// # Arguments
/// * `data` - The request data containing the result from the program server
///
///
/// # Returns
/// * A JSON response indicating the success of the operation
async fn handle_program_server_result(data: web::Json<WebhookPayload>) -> impl Responder {
let incoming = data.into_inner();

info!("Received program server result for E3 ID: {:?}", incoming.e3_id);
info!(
"Received program server result for E3 ID: {:?}",
incoming.e3_id
);

// Create the contract
let contract: EnclaveContract<ReadWrite> = match EnclaveContractFactory::create_write(
&CONFIG.http_rpc_url,
&CONFIG.enclave_address,
&CONFIG.private_key,
).await {
)
.await
{
Ok(contract) => contract,
Err(e) => {
info!("Failed to create contract: {:?}", e);
Expand All @@ -70,15 +79,17 @@ async fn handle_program_server_result(data: web::Json<WebhookPayload>) -> impl R
}
};

info!("Ciphertext output published successfully for E3 ID: {} with tx: {}", incoming.e3_id, pending_tx.transaction_hash);

info!(
"Ciphertext output published successfully for E3 ID: {} with tx: {}",
incoming.e3_id, pending_tx.transaction_hash
);

HttpResponse::Ok().json(format!(
"Ciphertext output published successfully for E3 ID: {}",
incoming.e3_id
))
}


/// Get the result for a given round
///
/// # Arguments
Expand Down Expand Up @@ -149,3 +160,24 @@ async fn get_round_state_lite(
Err(_) => HttpResponse::InternalServerError().body("Failed to get E3 state"),
}
}

/// Get the hashes of token holders for a given round
/// The hash is hash(address, token balance)
/// # Arguments
/// * `GetRoundRequest` - The request data containing the round ID
/// # Returns
/// * A JSON response containing the list of token holder hashes
async fn get_token_holders_hashes(
data: web::Json<GetRoundRequest>,
store: web::Data<AppData>,
) -> impl Responder {
let incoming = data.into_inner();

match store.e3(incoming.round_id).get_token_holder_hashes().await {
Ok(hashes) => HttpResponse::Ok().json(hashes),
Err(e) => {
error!("Error getting token holders hashes: {:?}", e);
HttpResponse::InternalServerError().body("Failed to get token holders hashes")
}
}
}
Loading
Loading