Skip to content

Commit

Permalink
soroban-cli: Warn or Error When Deploying Contracts Compiled with RC …
Browse files Browse the repository at this point in the history
…Version of Soroban SDK (#1061)

* Check if contract is compiled using RC SDK version in deploy

* Rust fmt

* Add info for --ignore-check

* Regenerate docs

* Update CLI tests

* Move rc checking logic to install cmd

* Update tests that use install cmd

* Formatting fix

* Gen docs

* rustfmt

* Reference fix
  • Loading branch information
stellarsaur authored and chadoh committed Nov 7, 2023
1 parent b6255e0 commit d57ed82
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 12 deletions.
2 changes: 2 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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"));
Expand Down
4 changes: 4 additions & 0 deletions cmd/soroban-cli/src/commands/contract/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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?;
Expand Down
53 changes: 51 additions & 2 deletions cmd/soroban-cli/src/commands/contract/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ 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;
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 {
Expand All @@ -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)]
Expand All @@ -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 {
Expand All @@ -55,6 +70,20 @@ impl Cmd {
}

pub async fn run_and_get_hash(&self) -> Result<Hash, Error> {
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
}

Expand Down Expand Up @@ -116,6 +145,26 @@ impl Cmd {
}
}

fn get_contract_meta_sdk_version(wasm_spec: &utils::contract_spec::ContractSpec) -> Option<String> {
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,
Expand Down
20 changes: 10 additions & 10 deletions cmd/soroban-rpc/internal/test/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,24 @@ 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())
}

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)
}

Expand All @@ -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))
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions docs/soroban-cli-full-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ Deploy a contract
* `--fee <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`



Expand Down Expand Up @@ -349,6 +352,9 @@ Install a WASM file to the ledger without creating a contract instance

Default value: `100`
* `--wasm <WASM>` — Path to wasm binary
* `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts

Default value: `false`



Expand Down

0 comments on commit d57ed82

Please sign in to comment.