diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index 951f20340..732f273dd 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -69,6 +69,7 @@ pub fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm) -> String { .arg("install") .arg("--wasm") .arg(wasm.path()) + .arg("--ignore-checks") .assert() .success() .stdout(format!("{hash}\n")); @@ -80,6 +81,7 @@ pub fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm) -> String { .arg(&format!("{hash}")) .arg("--salt") .arg(TEST_SALT) + .arg("--ignore-checks") .assert() .success() .stdout(format!("{TEST_CONTRACT_ID}\n")); diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs index 2b3cffb17..5ef18a833 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy.rs @@ -46,6 +46,9 @@ pub struct Cmd { config: config::Args, #[command(flatten)] pub fee: crate::fee::Args, + #[arg(long, short = 'i', default_value = "false")] + /// Whether to ignore safety checks when deploying contracts + pub ignore_checks: bool, } #[derive(thiserror::Error, Debug)] @@ -97,6 +100,7 @@ impl Cmd { wasm: wasm::Args { wasm: wasm.clone() }, config: self.config.clone(), fee: self.fee.clone(), + ignore_checks: self.ignore_checks, } .run_and_get_hash() .await?; diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 05e867bc9..a57d3f926 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -5,8 +5,8 @@ use std::num::ParseIntError; use clap::{command, Parser}; use soroban_env_host::xdr::{ Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, SequenceNumber, Transaction, TransactionExt, TransactionResult, - TransactionResultResult, Uint256, VecM, + OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, + TransactionExt, TransactionResult, TransactionResultResult, Uint256, VecM, }; use super::restore; @@ -14,6 +14,8 @@ use crate::key; use crate::rpc::{self, Client}; use crate::{commands::config, utils, wasm}; +const CONTRACT_META_SDK_KEY: &str = "rssdkver"; + #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { @@ -23,6 +25,9 @@ pub struct Cmd { pub fee: crate::fee::Args, #[command(flatten)] pub wasm: wasm::Args, + #[arg(long, short = 'i', default_value = "false")] + /// Whether to ignore safety checks when deploying contracts + pub ignore_checks: bool, } #[derive(thiserror::Error, Debug)] @@ -45,6 +50,16 @@ pub enum Error { UnexpectedSimulateTransactionResultSize { length: usize }, #[error(transparent)] Restore(#[from] restore::Error), + #[error("cannot parse WASM file {wasm}: {error}")] + CannotParseWasm { + wasm: std::path::PathBuf, + error: wasm::Error, + }, + #[error("the deployed smart contract {wasm} was built with Soroban Rust SDK v{version}, a release candidate version not intended for use with the Stellar Public Network. To deploy anyway, use --ignore-checks")] + ContractCompiledWithReleaseCandidateSdk { + wasm: std::path::PathBuf, + version: String, + }, } impl Cmd { @@ -55,6 +70,20 @@ impl Cmd { } pub async fn run_and_get_hash(&self) -> Result { + let wasm_spec = &self.wasm.parse().map_err(|e| Error::CannotParseWasm { + wasm: self.wasm.wasm.clone(), + error: e, + })?; + if let Some(rs_sdk_ver) = get_contract_meta_sdk_version(wasm_spec) { + if rs_sdk_ver.contains("rc") && !self.ignore_checks { + return Err(Error::ContractCompiledWithReleaseCandidateSdk { + wasm: self.wasm.wasm.clone(), + version: rs_sdk_ver, + }); + } else if rs_sdk_ver.contains("rc") { + tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); + } + } self.run_against_rpc_server(&self.wasm.read()?).await } @@ -116,6 +145,26 @@ impl Cmd { } } +fn get_contract_meta_sdk_version(wasm_spec: &utils::contract_spec::ContractSpec) -> Option { + let rs_sdk_version_option = if let Some(_meta) = &wasm_spec.meta_base64 { + wasm_spec.meta.iter().find(|entry| match entry { + ScMetaEntry::ScMetaV0(ScMetaV0 { key, .. }) => { + key.to_string_lossy().contains(CONTRACT_META_SDK_KEY) + } + }) + } else { + None + }; + if let Some(rs_sdk_version_entry) = &rs_sdk_version_option { + match rs_sdk_version_entry { + ScMetaEntry::ScMetaV0(ScMetaV0 { val, .. }) => { + return Some(val.to_string_lossy()); + } + } + } + None +} + pub(crate) fn build_install_contract_code_tx( source_code: &[u8], sequence: i64, diff --git a/cmd/soroban-rpc/internal/test/cli_test.go b/cmd/soroban-rpc/internal/test/cli_test.go index c83df3fce..4d3dc4448 100644 --- a/cmd/soroban-rpc/internal/test/cli_test.go +++ b/cmd/soroban-rpc/internal/test/cli_test.go @@ -63,7 +63,7 @@ func TestCLIWrapNative(t *testing.T) { func TestCLIContractInstall(t *testing.T) { NewCLITest(t) - output := runSuccessfulCLICmd(t, "contract install --wasm "+helloWorldContractPath) + output := runSuccessfulCLICmd(t, fmt.Sprintf("contract install --wasm %s --ignore-checks", helloWorldContractPath)) wasm := getHelloWorldContract(t) contractHash := xdr.Hash(sha256.Sum256(wasm)) require.Contains(t, output, contractHash.HexString()) @@ -71,16 +71,16 @@ func TestCLIContractInstall(t *testing.T) { func TestCLIContractInstallAndDeploy(t *testing.T) { NewCLITest(t) - runSuccessfulCLICmd(t, "contract install --wasm "+helloWorldContractPath) + runSuccessfulCLICmd(t, fmt.Sprintf("contract install --wasm %s --ignore-checks", helloWorldContractPath)) wasm := getHelloWorldContract(t) contractHash := xdr.Hash(sha256.Sum256(wasm)) - output := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt %s --wasm-hash %s", hex.EncodeToString(testSalt[:]), contractHash.HexString())) + output := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt %s --wasm-hash %s --ignore-checks", hex.EncodeToString(testSalt[:]), contractHash.HexString())) outputsContractIDInLastLine(t, output) } func TestCLIContractDeploy(t *testing.T) { NewCLITest(t) - output := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt %s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + output := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt %s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) outputsContractIDInLastLine(t, output) } @@ -100,14 +100,14 @@ func outputsContractIDInLastLine(t *testing.T, output string) { func TestCLIContractDeployAndInvoke(t *testing.T) { NewCLITest(t) - contractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + contractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) output := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- hello --world=world", contractID)) require.Contains(t, output, `["Hello","world"]`) } func TestCLIRestorePreamble(t *testing.T) { test := NewCLITest(t) - strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) count := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) require.Equal(t, "1", count) count = runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) @@ -125,7 +125,7 @@ func TestCLIRestorePreamble(t *testing.T) { func TestCLIBump(t *testing.T) { test := NewCLITest(t) - strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) count := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) require.Equal(t, "1", count) @@ -149,7 +149,7 @@ func TestCLIBump(t *testing.T) { } func TestCLIBumpTooLow(t *testing.T) { test := NewCLITest(t) - strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) count := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) require.Equal(t, "1", count) @@ -171,7 +171,7 @@ func TestCLIBumpTooLow(t *testing.T) { func TestCLIBumpTooHigh(t *testing.T) { test := NewCLITest(t) - strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) count := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) require.Equal(t, "1", count) @@ -190,7 +190,7 @@ func TestCLIBumpTooHigh(t *testing.T) { func TestCLIRestore(t *testing.T) { test := NewCLITest(t) - strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) + strkeyContractID := runSuccessfulCLICmd(t, fmt.Sprintf("contract deploy --salt=%s --wasm %s --ignore-checks", hex.EncodeToString(testSalt[:]), helloWorldContractPath)) count := runSuccessfulCLICmd(t, fmt.Sprintf("contract invoke --id %s -- inc", strkeyContractID)) require.Equal(t, "1", count) diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index 9298e789d..01ea2688c 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -283,6 +283,9 @@ Deploy a contract * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` +* `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts + + Default value: `false` @@ -349,6 +352,9 @@ Install a WASM file to the ledger without creating a contract instance Default value: `100` * `--wasm ` — Path to wasm binary +* `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts + + Default value: `false`