Skip to content

Commit 01eb014

Browse files
committed
Merge branch 'main' into refactor/provider
2 parents effe27b + 39fbd31 commit 01eb014

File tree

9 files changed

+905
-41
lines changed

9 files changed

+905
-41
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/katana/src/cli/rpc/client.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use katana_primitives::class::ClassHash;
88
use katana_primitives::contract::{ContractAddress, StorageKey};
99
use katana_primitives::transaction::TxHash;
1010
use katana_rpc_types::event::EventFilterWithPage;
11+
use katana_rpc_types::trie::ContractStorageKeys;
1112
use katana_rpc_types::FunctionCall;
1213
use serde_json::value::RawValue;
1314
use serde_json::Value;
@@ -90,6 +91,7 @@ const CHAIN_ID: &str = "starknet_chainId";
9091
const SYNCING: &str = "starknet_syncing";
9192
const GET_NONCE: &str = "starknet_getNonce";
9293
const GET_EVENTS: &str = "starknet_getEvents";
94+
const GET_STORAGE_PROOF: &str = "starknet_getStorageProof";
9395
const TRACE_TRANSACTION: &str = "starknet_traceTransaction";
9496
const TRACE_BLOCK_TRANSACTIONS: &str = "starknet_traceBlockTransactions";
9597

@@ -210,4 +212,18 @@ impl Client {
210212
pub async fn trace_block_transactions(&self, block_id: ConfirmedBlockIdOrTag) -> Result<Value> {
211213
self.send_request(TRACE_BLOCK_TRANSACTIONS, rpc_params!(block_id)).await
212214
}
215+
216+
pub async fn get_storage_proof(
217+
&self,
218+
block_id: BlockIdOrTag,
219+
class_hashes: Option<Vec<ClassHash>>,
220+
contract_addresses: Option<Vec<ContractAddress>>,
221+
contracts_storage_keys: Option<Vec<ContractStorageKeys>>,
222+
) -> Result<Value> {
223+
self.send_request(
224+
GET_STORAGE_PROOF,
225+
rpc_params!(block_id, class_hashes, contract_addresses, contracts_storage_keys),
226+
)
227+
.await
228+
}
213229
}

bin/katana/src/cli/rpc/starknet.rs

Lines changed: 226 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
use anyhow::{Context, Result};
24
use clap::{Args, Subcommand};
35
use katana_primitives::block::{BlockHash, BlockIdOrTag, BlockNumber, ConfirmedBlockIdOrTag};
@@ -7,6 +9,7 @@ use katana_primitives::execution::{EntryPointSelector, FunctionCall};
79
use katana_primitives::transaction::TxHash;
810
use katana_primitives::{ContractAddress, Felt};
911
use katana_rpc_types::event::{EventFilter, EventFilterWithPage, ResultPageRequest};
12+
use katana_rpc_types::trie::ContractStorageKeys;
1013

1114
use super::client::Client;
1215

@@ -91,6 +94,10 @@ pub enum StarknetCommands {
9194
/// Get execution traces for all transactions in a block
9295
#[command(name = "block-traces")]
9396
TraceBlockTransactions(TraceBlockTransactionsArg),
97+
98+
/// Get storage proofs for classes, contracts, and storage keys
99+
#[command(name = "proof")]
100+
GetStorageProof(GetStorageProofArgs),
94101
}
95102

96103
#[derive(Debug, Args)]
@@ -267,17 +274,21 @@ pub struct GetNonceArgs {
267274
#[derive(Debug, Args)]
268275
#[cfg_attr(test, derive(PartialEq, Eq))]
269276
pub struct GetStorageProofArgs {
270-
/// Class hashes JSON array
271-
#[arg(long)]
272-
class_hashes: Option<String>,
277+
/// Class hashes to get storage proofs for
278+
/// Example: --classes 0x1 0x2 0x3
279+
#[arg(long, num_args = 0..)]
280+
classes: Vec<String>,
273281

274-
/// Contract addresses JSON array
275-
#[arg(long)]
276-
contract_addresses: Option<String>,
282+
/// Contract addresses to get storage proofs for
283+
/// Example: --contracts 0x1 0x2 0x3
284+
#[arg(long, num_args = 0..)]
285+
contracts: Vec<String>,
277286

278-
/// Contract storage keys JSON
279-
#[arg(long)]
280-
contracts_storage_keys: Option<String>,
287+
/// Contract storage keys in format: address,key address,key
288+
/// Multiple keys for same address are supported: 0x123,0x1 0x123,0x2
289+
/// Example: --storages 0x1234,0xabc 0x5678,0xdef
290+
#[arg(long, num_args = 0..)]
291+
storages: Vec<String>,
281292

282293
/// Block ID (number, hash, 'latest', or 'pending'). Defaults to 'latest'
283294
#[arg(long)]
@@ -455,6 +466,64 @@ impl StarknetCommands {
455466
let result = client.trace_block_transactions(block_id.0).await?;
456467
println!("{}", colored_json::to_colored_json_auto(&result)?);
457468
}
469+
470+
StarknetCommands::GetStorageProof(args) => {
471+
let block_id = args.block.0;
472+
473+
// Parse class_hashes if provided
474+
let class_hashes: Option<Vec<ClassHash>> = if !args.classes.is_empty() {
475+
Some(
476+
args.classes
477+
.iter()
478+
.enumerate()
479+
.map(|(i, s)| {
480+
s.trim().parse::<ClassHash>().with_context(|| {
481+
format!("Invalid class hash at position {}: '{}'", i, s)
482+
})
483+
})
484+
.collect::<Result<Vec<_>>>()?,
485+
)
486+
} else {
487+
None
488+
};
489+
490+
// Parse contract_addresses if provided
491+
let contract_addresses: Option<Vec<ContractAddress>> = if !args.contracts.is_empty()
492+
{
493+
Some(
494+
args.contracts
495+
.iter()
496+
.enumerate()
497+
.map(|(i, s)| {
498+
s.trim().parse::<ContractAddress>().with_context(|| {
499+
format!("Invalid contract address at position {}: '{}'", i, s)
500+
})
501+
})
502+
.collect::<Result<Vec<_>>>()?,
503+
)
504+
} else {
505+
None
506+
};
507+
508+
// Parse contracts_storage_keys if provided
509+
let contracts_storage_keys: Option<Vec<ContractStorageKeys>> =
510+
if !args.storages.is_empty() {
511+
Some(parse_contract_storage_keys(&args.storages)?)
512+
} else {
513+
None
514+
};
515+
516+
let result = client
517+
.get_storage_proof(
518+
block_id,
519+
class_hashes,
520+
contract_addresses,
521+
contracts_storage_keys,
522+
)
523+
.await?;
524+
525+
println!("{}", colored_json::to_colored_json_auto(&result)?);
526+
}
458527
}
459528
Ok(())
460529
}
@@ -545,6 +614,42 @@ fn parse_event_keys(keys: &[String]) -> Result<Vec<Vec<Felt>>> {
545614
.collect()
546615
}
547616

617+
/// Parses contract storage keys from CLI arguments.
618+
///
619+
/// Format: Each argument is "address,key" where address is a contract address and key is a storage
620+
/// key. Multiple entries with the same address will be grouped together.
621+
/// Example: ["0x123,0x1", "0x123,0x2", "0x456,0x3"] =>
622+
/// [ContractStorageKeys { address: 0x123, keys: [0x1, 0x2] },
623+
/// ContractStorageKeys { address: 0x456, keys: [0x3] }]
624+
fn parse_contract_storage_keys(storages: &[String]) -> Result<Vec<ContractStorageKeys>> {
625+
let mut map: HashMap<ContractAddress, Vec<StorageKey>> = HashMap::new();
626+
627+
for (i, pair) in storages.iter().enumerate() {
628+
let parts: Vec<&str> = pair.split(',').collect();
629+
630+
if parts.len() != 2 {
631+
anyhow::bail!(
632+
"invalid storage format at position {}: '{}'. Expected 'address,key'",
633+
i,
634+
pair
635+
);
636+
}
637+
638+
let address = parts[0].trim().parse::<ContractAddress>().with_context(|| {
639+
format!("invalid contract address at position {}: '{}'", i, parts[0])
640+
})?;
641+
642+
let key = parts[1]
643+
.trim()
644+
.parse::<StorageKey>()
645+
.with_context(|| format!("invalid storage key at position {}: '{}'", i, parts[1]))?;
646+
647+
map.entry(address).or_default().push(key);
648+
}
649+
650+
Ok(map.into_iter().map(|(address, keys)| ContractStorageKeys { address, keys }).collect())
651+
}
652+
548653
#[cfg(test)]
549654
mod tests {
550655
use std::str::FromStr;
@@ -554,6 +659,7 @@ mod tests {
554659
use katana_primitives::felt;
555660

556661
use super::{BlockIdArg, ConfirmedBlockIdArg};
662+
use crate::cli::rpc::starknet::GetStorageProofArgs;
557663

558664
#[test]
559665
fn block_id_arg_from_str() {
@@ -623,18 +729,20 @@ mod tests {
623729
}
624730

625731
use clap::Parser;
732+
use katana_primitives::contract::StorageKey;
733+
use katana_primitives::ContractAddress;
626734

627-
use super::{parse_event_keys, GetEventsArgs};
735+
use super::{parse_contract_storage_keys, parse_event_keys, GetEventsArgs};
628736

629737
#[derive(Debug, Parser)]
630-
struct TestCli {
738+
struct TestEventCli {
631739
#[command(flatten)]
632740
args: GetEventsArgs,
633741
}
634742

635743
#[test]
636744
fn get_events_args_single_keys() {
637-
let args = TestCli::try_parse_from(["test", "--keys", "0x1", "0x2", "0x3"]).unwrap();
745+
let args = TestEventCli::try_parse_from(["test", "--keys", "0x1", "0x2", "0x3"]).unwrap();
638746

639747
let keys = parse_event_keys(&args.args.keys).unwrap();
640748
assert_eq!(keys.len(), 3);
@@ -646,7 +754,8 @@ mod tests {
646754
#[test]
647755
fn get_events_args_multiple_keys() {
648756
let args =
649-
TestCli::try_parse_from(["test", "--keys", "0x9", "0x1,0x2,0x3", "0x4,0x5"]).unwrap();
757+
TestEventCli::try_parse_from(["test", "--keys", "0x9", "0x1,0x2,0x3", "0x4,0x5"])
758+
.unwrap();
650759

651760
let keys = parse_event_keys(&args.args.keys).unwrap();
652761
assert_eq!(keys.len(), 3);
@@ -657,7 +766,7 @@ mod tests {
657766

658767
#[test]
659768
fn get_events_args_keys_with_whitespace() {
660-
let args = TestCli::try_parse_from(["test", "--keys", "0x1, 0x2 , 0x3"]).unwrap();
769+
let args = TestEventCli::try_parse_from(["test", "--keys", "0x1, 0x2 , 0x3"]).unwrap();
661770

662771
let keys = parse_event_keys(&args.args.keys).unwrap();
663772
assert_eq!(keys.len(), 1);
@@ -666,7 +775,7 @@ mod tests {
666775

667776
#[test]
668777
fn get_events_args_invalid_felt() {
669-
let args = TestCli::try_parse_from(["test", "--keys", "0x1", "invalid"]).unwrap();
778+
let args = TestEventCli::try_parse_from(["test", "--keys", "0x1", "invalid"]).unwrap();
670779

671780
let result = parse_event_keys(&args.args.keys);
672781
assert!(result.is_err());
@@ -675,10 +784,111 @@ mod tests {
675784

676785
#[test]
677786
fn get_events_args_invalid_hex() {
678-
let args = TestCli::try_parse_from(["test", "--keys", "0x1,0xGGG"]).unwrap();
787+
let args = TestEventCli::try_parse_from(["test", "--keys", "0x1,0xGGG"]).unwrap();
679788

680789
let result = parse_event_keys(&args.args.keys);
681790
assert!(result.is_err());
682791
assert!(result.unwrap_err().to_string().contains("invalid felt in key group 0"));
683792
}
793+
794+
#[derive(Debug, Parser)]
795+
struct TestProofCli {
796+
#[command(flatten)]
797+
args: GetStorageProofArgs,
798+
}
799+
800+
#[test]
801+
fn get_contract_storage_proof_keys_single_pair() {
802+
let args = TestProofCli::try_parse_from(["test", "--storages", "0x123,0xabc"]).unwrap();
803+
let result = parse_contract_storage_keys(&args.args.storages).unwrap();
804+
805+
assert_eq!(result.len(), 1);
806+
assert_eq!(result[0].address, ContractAddress::from(felt!("0x123")));
807+
assert_eq!(result[0].keys.len(), 1);
808+
assert_eq!(result[0].keys[0], StorageKey::from(felt!("0xabc")));
809+
}
810+
811+
#[test]
812+
fn get_contract_storage_proof_keys_multiple_pairs_same_address() {
813+
let args = TestProofCli::try_parse_from([
814+
"test",
815+
"--storages",
816+
"0x123,0x1",
817+
"0x123,0x2",
818+
"0x123,0x3",
819+
])
820+
.unwrap();
821+
let result = parse_contract_storage_keys(&args.args.storages).unwrap();
822+
823+
assert_eq!(result.len(), 1);
824+
assert_eq!(result[0].address, ContractAddress::from(felt!("0x123")));
825+
assert_eq!(result[0].keys.len(), 3);
826+
assert!(result[0].keys.contains(&StorageKey::from(felt!("0x1"))));
827+
assert!(result[0].keys.contains(&StorageKey::from(felt!("0x2"))));
828+
assert!(result[0].keys.contains(&StorageKey::from(felt!("0x3"))));
829+
}
830+
831+
#[test]
832+
fn get_contract_storage_proof_keys_multiple_addresses() {
833+
let args = TestProofCli::try_parse_from([
834+
"test",
835+
"--storages",
836+
"0x123,0xabc",
837+
"0x456,0xdef",
838+
"0x789,0x111",
839+
])
840+
.unwrap();
841+
let result = parse_contract_storage_keys(&args.args.storages).unwrap();
842+
843+
assert_eq!(result.len(), 3);
844+
845+
let addr_123 =
846+
result.iter().find(|r| r.address == ContractAddress::from(felt!("0x123"))).unwrap();
847+
assert_eq!(addr_123.keys.len(), 1);
848+
assert_eq!(addr_123.keys[0], StorageKey::from(felt!("0xabc")));
849+
850+
let addr_456 =
851+
result.iter().find(|r| r.address == ContractAddress::from(felt!("0x456"))).unwrap();
852+
assert_eq!(addr_456.keys.len(), 1);
853+
assert_eq!(addr_456.keys[0], StorageKey::from(felt!("0xdef")));
854+
855+
let addr_789 =
856+
result.iter().find(|r| r.address == ContractAddress::from(felt!("0x789"))).unwrap();
857+
assert_eq!(addr_789.keys.len(), 1);
858+
assert_eq!(addr_789.keys[0], StorageKey::from(felt!("0x111")));
859+
}
860+
861+
#[test]
862+
fn parse_contract_storage_keys_with_whitespace() {
863+
let args = TestProofCli::try_parse_from(["test", "--storages", " 0x123 , 0xabc "]).unwrap();
864+
let result = parse_contract_storage_keys(&args.args.storages).unwrap();
865+
866+
assert_eq!(result.len(), 1);
867+
assert_eq!(result[0].address, ContractAddress::from(felt!("0x123")));
868+
assert_eq!(result[0].keys[0], StorageKey::from(felt!("0xabc")));
869+
}
870+
871+
#[test]
872+
fn parse_contract_storage_keys_invalid_format() {
873+
let input = vec!["0x123".to_string()];
874+
let result = parse_contract_storage_keys(&input);
875+
assert!(result.is_err());
876+
assert!(result.unwrap_err().to_string().contains("invalid storage format"));
877+
}
878+
879+
#[test]
880+
fn parse_contract_storage_keys_invalid_address() {
881+
let input = vec!["invalid,0xabc".to_string()];
882+
let result = parse_contract_storage_keys(&input);
883+
assert!(result.is_err());
884+
assert!(result.unwrap_err().to_string().contains("invalid contract address"));
885+
}
886+
887+
#[test]
888+
fn parse_contract_storage_keys_invalid_key() {
889+
let input = vec!["0x123,invalid".to_string()];
890+
let result = parse_contract_storage_keys(&input);
891+
assert!(result.is_err());
892+
assert!(result.unwrap_err().to_string().contains("invalid storage key"));
893+
}
684894
}

0 commit comments

Comments
 (0)