diff --git a/cmd/soroban-cli/src/commands/config/data.rs b/cmd/soroban-cli/src/commands/config/data.rs index 7fd9efb09c..9c5b4ec1f7 100644 --- a/cmd/soroban-cli/src/commands/config/data.rs +++ b/cmd/soroban-cli/src/commands/config/data.rs @@ -18,6 +18,8 @@ pub enum Error { Http(#[from] http::uri::InvalidUri), #[error(transparent)] Ulid(#[from] ulid::DecodeError), + #[error(transparent)] + Xdr(#[from] xdr::Error), } pub const XDG_DATA_HOME: &str = "XDG_DATA_HOME"; @@ -37,6 +39,12 @@ pub fn actions_dir() -> Result { Ok(dir) } +pub fn spec_dir() -> Result { + let dir = project_dir()?.data_local_dir().join("spec"); + std::fs::create_dir_all(&dir)?; + Ok(dir) +} + pub fn write(action: Action, rpc_url: Uri) -> Result { let data = Data { action, @@ -54,6 +62,23 @@ pub fn read(id: &ulid::Ulid) -> Result<(Action, Uri), Error> { Ok((data.action, http::Uri::from_str(&data.rpc_url)?)) } +pub fn write_spec(hash: &str, spec_entries: &[xdr::ScSpecEntry]) -> Result<(), Error> { + let file = spec_dir()?.join(hash); + tracing::trace!("writing spec to {:?}", file); + let mut contents: Vec = Vec::new(); + for entry in spec_entries { + contents.extend(entry.to_xdr(xdr::Limits::none())?); + } + std::fs::write(file, contents)?; + Ok(()) +} + +pub fn read_spec(hash: &str) -> Result, Error> { + let file = spec_dir()?.join(hash); + tracing::trace!("reading spec from {:?}", file); + Ok(soroban_spec::read::parse_raw(&std::fs::read(file)?)?) +} + pub fn list_ulids() -> Result, Error> { let dir = actions_dir()?; let mut list = std::fs::read_dir(dir)? diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 1b077bd3f9..ec9de88055 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -165,7 +165,9 @@ impl NetworkRunnable for Cmd { .run_against_rpc_server(args, None) .await?; } - + if args.map_or(true, |a| !a.no_cache) { + data::write_spec(&hash.to_string(), &wasm_spec.spec)?; + } Ok(hash) } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 68eadc922d..d7a890334c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,11 +12,11 @@ use heck::ToKebabCase; use soroban_env_host::{ xdr::{ - self, Error as XdrError, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, - Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, - SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, TransactionExt, - Uint256, VecM, + self, ContractDataEntry, Error as XdrError, Hash, HostFunction, InvokeContractArgs, + InvokeHostFunctionOp, LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, + OperationBody, Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, + ScVal, ScVec, SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, + TransactionExt, Uint256, VecM, }, HostError, }; @@ -331,8 +331,29 @@ impl NetworkRunnable for Cmd { let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); + let ContractDataEntry { + val: + xdr::ScVal::ContractInstance(xdr::ScContractInstance { + executable: xdr::ContractExecutable::Wasm(hash), + .. + }), + .. + } = client.get_contract_data(&contract_id).await? + else { + return Err(Error::MissingResult); + }; + let hash = hash.to_string(); + // Get the contract - let spec_entries = client.get_remote_contract_spec(&contract_id).await?; + let spec_entries = if let Ok(entries) = data::read_spec(&hash) { + entries + } else { + let res = client.get_remote_contract_spec(&contract_id).await?; + if global_args.map_or(true, |a| !a.no_cache) { + data::write_spec(&hash, &res)?; + } + res + }; // Get the ledger footprint let (function, spec, host_function_params, signers) =