Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

soroban-cli: Warn or Error When Deploying Contracts Compiled with RC Version of Soroban SDK #1061

Merged
merged 11 commits into from
Nov 1, 2023
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 @@ -117,6 +146,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 @@ -66,24 +66,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 @@ -103,14 +103,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 @@ -128,7 +128,7 @@ func TestCLIRestorePreamble(t *testing.T) {

func TestCLIExtend(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 @@ -152,7 +152,7 @@ func TestCLIExtend(t *testing.T) {
}
func TestCLIExtendTooLow(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 @@ -174,7 +174,7 @@ func TestCLIExtendTooLow(t *testing.T) {

func TestCLIExtendTooHigh(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 @@ -193,7 +193,7 @@ func TestCLIExtendTooHigh(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
Loading