Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.
Closed
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
324 changes: 18 additions & 306 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,18 @@ futures = "0.3.30"
reqwest = { version = "0.12.5", features = ["json"] }
serde = "1.0.204"
serde_json = "1.0.121"
semver = "1.0"
tokio = { version = "1.39.2", features = ["full"] }
tokio-util = "0.7.15"
zerocopy = "0.7.35"
hyper = { version = "1.4.1", features = ["full"] }
util = "0.1.3"
http-body-util = "0.1.2"
simd-json = "0.13.10"
rand = "0.8.5"
hyper-util = "0.1.6"
bincode = "1.3.3"
tokio-stream = "0.1.15"
sled = { version = "1.0.0-alpha.122" }
ecdsa = { version = "0.16.9", features = ["serde", "signing"] }
k256 = "0.13.4"
hex = "0.4.3"
tempfile = { version = "3.12.0" }
mockall = { version = "0.13.0" }
wiremock = "0.6.1"
directories = "6.0.0"
bollard = "0.18"

tracing = "0.1.41"
tracing-subscriber = {version = "0.3", features = ["std", "env-filter"]}

22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,23 @@ Parameters:
"result": {
"solidity_source": "contract MyAssertion { ... }",
"bytecode": "0x...",
"signature": "0x..."
"prover_signature": "0x...",
"encoded_constructor_args": "",
"constructor_abi_signature": "constructor()"
"constructor_abi_signature": "constructor()",
"assertion_contract_name": "MyAssertion",
"compiler_version": "0.8.17",
"abi": [{ "type": "function", "name": "check", "inputs": [] }]
},
"id": 1
}
```

`abi` is the exact JSON ABI emitted by the selected Solidity compiler and is
stored when the assertion is submitted. Existing records are recompiled and
backfilled on first read after verifying that the compiler bytecode matches the
stored deployment bytecode. Compiler metadata is omitted only for raw-bytecode
submissions or legacy records that cannot be safely reconstructed.

### Error Codes

The API uses the following error codes:
Expand Down Expand Up @@ -227,9 +236,12 @@ curl -X POST -H "Content-Type: application/json" http://localhost:5001 -d '{
{
"solidity_source": "contract MyAssertion { ... }",
"bytecode": "0x...",
"signature": "0x..."
"prover_signature": "0x...",
"encoded_constructor_args": "0x0000000000000000000000000000000000000000000000000000000000000005",
"constructor_abi_signature": "constructor(uint256)"
"constructor_abi_signature": "constructor(uint256)",
"assertion_contract_name": "MyAssertion",
"compiler_version": "0.8.17",
"abi": []
},
],
"id": 1
Expand All @@ -238,6 +250,6 @@ curl -X POST -H "Content-Type: application/json" http://localhost:5001 -d '{

## Limitations

- Only one contract per Solidity source file is supported
- The contract selected by `assertion_contract_name` is returned when a source file defines multiple contracts
- Compilation is performed using Docker containers
- Supported Solidity versions depend on available [ethereum/solc](https://hub.docker.com/r/ethereum/solc) images
1 change: 0 additions & 1 deletion crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ alloy.workspace = true
bincode.workspace = true
sled.workspace = true
tempfile.workspace = true
mockall.workspace = true
wiremock.workspace = true
bollard.workspace = true
51 changes: 36 additions & 15 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ struct JsonRpcError {

impl DaClient {
/// Create a new DA client
///
/// # Errors
///
/// Returns an error when `da_url` is invalid or the HTTP client cannot be built.
pub fn new(da_url: &str) -> Result<Self, DaClientError> {
let base_url = Url::parse(da_url)?;
let client = Client::builder().use_rustls_tls().build()?;
Expand All @@ -88,6 +92,11 @@ impl DaClient {
}

/// Create a new DA client with authentication
///
/// # Errors
///
/// Returns an error when the URL or authorization header is invalid, or when the HTTP client
/// cannot be built.
pub fn new_with_auth(da_url: &str, auth: &str) -> Result<Self, DaClientError> {
let base_url = Url::parse(da_url)?;
let mut headers = header::HeaderMap::new();
Expand Down Expand Up @@ -174,6 +183,10 @@ impl DaClient {
}

/// Fetch the bytecode and signature for the given assertion id from the DA layer
///
/// # Errors
///
/// Returns an error when the request fails or the DA returns an invalid JSON-RPC response.
pub async fn fetch_assertion(
&self,
assertion_id: B256,
Expand All @@ -183,6 +196,10 @@ impl DaClient {
}

/// Submit the assertion bytecode to the DA layer
///
/// # Errors
///
/// Returns an error when the request fails or the DA returns an invalid JSON-RPC response.
pub async fn submit_assertion(
&self,
assertion_contract_name: String,
Expand All @@ -200,6 +217,10 @@ impl DaClient {
}

/// Submit the assertion bytecode with constructor args to the DA layer
///
/// # Errors
///
/// Returns an error when the request fails or the DA returns an invalid JSON-RPC response.
pub async fn submit_assertion_with_args(
&self,
assertion_contract_name: String,
Expand Down Expand Up @@ -292,7 +313,7 @@ mod tests {

// Start the database listener
tokio::spawn(async move {
listen_for_db(db_receiver, db, CancellationToken::new())
Box::pin(listen_for_db(db_receiver, db, CancellationToken::new()))
.await
.unwrap();
});
Expand All @@ -305,9 +326,9 @@ mod tests {

#[tokio::test]
async fn test_client_submit_solidity_assertion() {
let (_temp_dir, _db_sender, _signer, client) = setup_test_env().await;
let (_temp_dir, _db_sender, _signer, client) = Box::pin(setup_test_env()).await;

let source_code = r#"
let source_code = r"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

Expand All @@ -322,7 +343,7 @@ mod tests {
return value;
}
}
"#;
";

let response = client
.submit_assertion(
Expand All @@ -339,7 +360,7 @@ mod tests {

#[tokio::test]
async fn test_client_get_assertion() {
let (_temp_dir, db_sender, signer, client) = setup_test_env().await;
let (_temp_dir, db_sender, signer, client) = Box::pin(setup_test_env()).await;

// First submit an assertion directly to DB
let source_code = "contract Test { }";
Expand All @@ -348,7 +369,7 @@ mod tests {
bytecode: vec![1, 2, 3, 4],
prover_signature: signer.sign_hash(&keccak256([1, 2, 3, 4])).await.unwrap(),
assertion_contract_name: "Test".to_string(),
compiler_version: "0.8.17".to_string(),
compiler_version: "legacy".to_string(),
constructor_abi_signature: "constructor()".to_string(),
encoded_constructor_args: Bytes::new(),
};
Expand Down Expand Up @@ -376,7 +397,7 @@ mod tests {

#[tokio::test]
async fn test_get_nonexistent_assertion() {
let (_temp_dir, _db_sender, _signer, client) = setup_test_env().await;
let (_temp_dir, _db_sender, _signer, client) = Box::pin(setup_test_env()).await;

let nonexistent_id = B256::ZERO;
let result = client.fetch_assertion(nonexistent_id).await;
Expand Down Expand Up @@ -425,9 +446,9 @@ mod tests {

#[tokio::test]
async fn test_client_submit_solidity_assertion_with_args() {
let (_temp_dir, _db_sender, _signer, client) = setup_test_env().await;
let (_temp_dir, _db_sender, _signer, client) = Box::pin(setup_test_env()).await;

let source_code = r#"
let source_code = r"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

Expand All @@ -442,7 +463,7 @@ mod tests {
return value;
}
}
"#;
";

let response = client
.submit_assertion_with_args(
Expand All @@ -461,7 +482,7 @@ mod tests {

#[tokio::test]
async fn test_invalid_solidity_submission() {
let (_temp_dir, _db_sender, _signer, client) = setup_test_env().await;
let (_temp_dir, _db_sender, _signer, client) = Box::pin(setup_test_env()).await;

// Invalid Solidity code (missing semicolon)
let invalid_source = "contract Test { uint256 x = 5 }";
Expand Down Expand Up @@ -509,7 +530,7 @@ mod tests {

#[tokio::test]
async fn test_response_content_validation() {
let (_temp_dir, db_sender, signer, client) = setup_test_env().await;
let (_temp_dir, db_sender, signer, client) = Box::pin(setup_test_env()).await;

// Create a test contract with specific bytecode
let source_code = "contract Test { uint256 value; }";
Expand All @@ -523,7 +544,7 @@ mod tests {
bytecode: bytecode.clone(),
prover_signature: signature,
assertion_contract_name: "Test".to_string(),
compiler_version: "0.8.17".to_string(),
compiler_version: "legacy".to_string(),
constructor_abi_signature: "constructor()".to_string(),
encoded_constructor_args: Bytes::new(),
};
Expand Down Expand Up @@ -583,7 +604,7 @@ mod tests {
.mount(&mock_server)
.await;

let result = client.fetch_assertion(Default::default()).await;
let result = client.fetch_assertion(B256::default()).await;
assert!(result.is_err());
match result.unwrap_err() {
DaClientError::InvalidResponse(msg) => {
Expand Down Expand Up @@ -614,7 +635,7 @@ mod tests {
.mount(&mock_server)
.await;

let result = client.fetch_assertion(Default::default()).await;
let result = client.fetch_assertion(B256::default()).await;
assert!(result.is_err());
match result.unwrap_err() {
DaClientError::InvalidResponse(msg) => {
Expand Down
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ edition = "2024"

[dependencies]
serde.workspace = true
serde_json.workspace = true
alloy.workspace = true
33 changes: 32 additions & 1 deletion crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloy::json_abi::JsonAbi;
use alloy::primitives::{
B256,
Bytes,
Expand Down Expand Up @@ -25,11 +26,41 @@ pub struct DaSubmissionResponse {
}

///The response from the DA layer when fetching an assertion
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DaFetchResponse {
pub solidity_source: String,
pub bytecode: Bytes,
pub prover_signature: Bytes,
pub encoded_constructor_args: Bytes,
pub constructor_abi_signature: String,
/// Producer-selected assertion contract. Absent on older servers.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assertion_contract_name: Option<String>,
/// Compiler used for the stored artifact. Absent on older servers.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compiler_version: Option<String>,
/// Exact compiler-produced ABI. Absent only for raw or unrecoverable legacy artifacts.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abi: Option<JsonAbi>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn deserializes_legacy_fetch_response_without_compiler_metadata() {
let response: DaFetchResponse = serde_json::from_value(serde_json::json!({
"solidity_source": "contract Assertion {}",
"bytecode": "0x6000",
"prover_signature": "0x",
"encoded_constructor_args": "0x",
"constructor_abi_signature": "constructor()"
}))
.unwrap();

assert!(response.assertion_contract_name.is_none());
assert!(response.compiler_version.is_none());
assert!(response.abi.is_none());
}
}
16 changes: 2 additions & 14 deletions crates/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,13 @@ futures.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
semver.workspace = true
tokio.workspace = true
zerocopy.workspace = true
hyper.workspace = true
util.workspace = true
http-body-util.workspace = true
simd-json.workspace = true
rand.workspace = true
hyper-util.workspace = true
bincode.workspace = true
tokio-stream.workspace = true
sled.workspace = true
ecdsa.workspace = true
k256.workspace = true
hex.workspace = true
directories.workspace = true
bollard.workspace = true
Expand All @@ -40,21 +34,15 @@ tokio-util.workspace = true

tracing.workspace = true
rust-tracing.workspace = true
tracing-subscriber.workspace = true
futures-util = "0.3"
uuid = { version = "1.15", features = ["v4"] }
tempfile = "3.18"
metrics-exporter-prometheus = "0.16"
metrics = "0.24"
metrics = "0.24.6"

thiserror = "2"
regex = "1"

assertion-da-core = { path = "../core" }

[dev-dependencies]
tempfile.workspace = true
mockall.workspace = true
wiremock.workspace = true
once_cell = "1.21"
assertion-da-client = { path = "../client" }
Loading