Skip to content

Commit

Permalink
Support for constructor-related functionality in Soroban SDK. (#1327)
Browse files Browse the repository at this point in the history
### What

The main non-test changes are updates for the auth-related data
structures and a wrapper for the new deployer function
(`create_contract_with_constructor`).

The remaining changes concern the test infrastructure support,
specifically contract creation utilities that support passing
constructor arguments.

### Why

Supporting constructors introduced in protocol 22.

### Known limitations

N/A
  • Loading branch information
dmkozh authored Sep 16, 2024
1 parent 9e35dce commit 6ede50a
Show file tree
Hide file tree
Showing 132 changed files with 6,016 additions and 282 deletions.
183 changes: 124 additions & 59 deletions Cargo.lock

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,46 @@ members = [
]

[workspace.package]
version = "21.7.1"
rust-version = "1.74.0"
version = "22.0.0-rc.1"
rust-version = "1.79.0"

[workspace.dependencies]
soroban-sdk = { version = "21.7.1", path = "soroban-sdk" }
soroban-sdk-macros = { version = "21.7.1", path = "soroban-sdk-macros" }
soroban-spec = { version = "21.7.1", path = "soroban-spec" }
soroban-spec-rust = { version = "21.7.1", path = "soroban-spec-rust" }
soroban-ledger-snapshot = { version = "21.7.1", path = "soroban-ledger-snapshot" }
soroban-token-sdk = { version = "21.7.1", path = "soroban-token-sdk" }
soroban-sdk = { version = "22.0.0-rc.1", path = "soroban-sdk" }
soroban-sdk-macros = { version = "22.0.0-rc.1", path = "soroban-sdk-macros" }
soroban-spec = { version = "22.0.0-rc.1", path = "soroban-spec" }
soroban-spec-rust = { version = "22.0.0-rc.1", path = "soroban-spec-rust" }
soroban-ledger-snapshot = { version = "22.0.0-rc.1", path = "soroban-ledger-snapshot" }
soroban-token-sdk = { version = "22.0.0-rc.1", path = "soroban-token-sdk" }

[workspace.dependencies.soroban-env-common]
version = "=21.2.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "0c918ac2bd808ba1a850281c6b1c731e7fe50c53"
version = "=22.0.0"
git = "https://github.com/stellar/rs-soroban-env"
rev = "75b782119942a4c8be8003f2901db38b30b6db2d"

[workspace.dependencies.soroban-env-guest]
version = "=21.2.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "0c918ac2bd808ba1a850281c6b1c731e7fe50c53"
version = "=22.0.0"
git = "https://github.com/stellar/rs-soroban-env"
rev = "75b782119942a4c8be8003f2901db38b30b6db2d"

[workspace.dependencies.soroban-env-host]
version = "=21.2.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "0c918ac2bd808ba1a850281c6b1c731e7fe50c53"
version = "=22.0.0"
git = "https://github.com/stellar/rs-soroban-env"
rev = "75b782119942a4c8be8003f2901db38b30b6db2d"

[workspace.dependencies.stellar-strkey]
version = "=0.0.8"

[workspace.dependencies.stellar-xdr]
version = "=21.2.0"
version = "=22.0.0"
default-features = false
features = ["curr"]
# git = "https://github.com/stellar/rs-stellar-xdr"
# rev = "d0138770652a615e3cd99447f2f2727658c17450"
git = "https://github.com/stellar/rs-stellar-xdr"
rev = "39d7dbb0c12bd422ee43a6e2e3277789da4eaac8"

#[patch."https://github.com/stellar/rs-soroban-env"]
#soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" }
#soroban-env-guest = { path = "../rs-soroban-env/soroban-env-guest" }
#soroban-env-host = { path = "../rs-soroban-env/soroban-env-host/" }
#soroban-env-host = { path = "../rs-soroban-env/soroban-env-host" }
#[patch."https://github.com/stellar/rs-stellar-xdr"]
#stellar-xdr = { path = "../rs-stellar-xdr/" }

Expand Down
2 changes: 1 addition & 1 deletion soroban-ledger-snapshot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl LedgerSnapshot {
impl Default for LedgerSnapshot {
fn default() -> Self {
Self {
protocol_version: 20,
protocol_version: 22,
sequence_number: Default::default(),
timestamp: Default::default(),
network_id: Default::default(),
Expand Down
4 changes: 4 additions & 0 deletions soroban-sdk/doctest_fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@

Files contained in this directory are used within examples inside docs, i.e.
doctests.

`contract.wasm` is a copy of `test_add_u64` test contract.

`contract_with_constructor.wasm` is a copy of `test_constructor` test contract.
Binary file modified soroban-sdk/doctest_fixtures/contract.wasm
Binary file not shown.
Binary file not shown.
20 changes: 20 additions & 0 deletions soroban-sdk/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ use crate::{contracttype, crypto::Hash, Address, BytesN, Env, Error, Symbol, Val
#[derive(Clone)]
#[contracttype(crate_path = "crate", export = false)]
pub enum Context {
/// Contract invocation.
Contract(ContractContext),
/// Contract that has a constructor with no arguments is created.
CreateContractHostFn(CreateContractHostFnContext),
/// Contract that has a constructor with 1 or more arguments is created.
CreateContractWithCtorHostFn(CreateContractWithConstructorHostFnContext),
}

/// Authorization context of a single contract call.
Expand All @@ -35,6 +39,18 @@ pub struct CreateContractHostFnContext {
pub salt: BytesN<32>,
}

/// Authorization context for `create_contract` host function that creates a
/// new contract on behalf of authorizer address.
/// This is the same as `CreateContractHostFnContext`, but also has
/// contract constructor arguments.
#[derive(Clone)]
#[contracttype(crate_path = "crate", export = false)]
pub struct CreateContractWithConstructorHostFnContext {
pub executable: ContractExecutable,
pub salt: BytesN<32>,
pub constructor_args: Vec<Val>,
}

/// Contract executable used for creating a new contract and used in
/// `CreateContractHostFnContext`.
#[derive(Clone)]
Expand All @@ -53,8 +69,12 @@ pub enum ContractExecutable {
#[derive(Clone)]
#[contracttype(crate_path = "crate", export = false)]
pub enum InvokerContractAuthEntry {
/// Invoke a contract.
Contract(SubContractInvocation),
/// Create a contract passing 0 arguments to constructor.
CreateContractHostFn(CreateContractHostFnContext),
/// Create a contract passing 0 or more arguments to constructor.
CreateContractWithCtorHostFn(CreateContractWithConstructorHostFnContext),
}

/// Value of contract node in InvokerContractAuthEntry tree.
Expand Down
111 changes: 88 additions & 23 deletions soroban-sdk/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,79 @@
//!
//! ### Examples
//!
//! #### Deploy a contract without constructor (or 0-argument constructor)
//!
//! ```
//! # use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol};
//! #
//! # #[contract]
//! # pub struct Contract;
//! #
//! # #[contractimpl]
//! # impl Contract {
//! # pub fn f(env: Env, wasm_hash: BytesN<32>) {
//! # let salt = [0u8; 32];
//! # let deployer = env.deployer().with_current_contract(salt);
//! # // Deployed contract address is deterministic and can be accessed
//! # // before deploying the contract.
//! # let _ = deployer.deployed_address();
//! # let contract_address = deployer.deploy(wasm_hash);
//! # }
//! use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol};
//!
//! const DEPLOYED_WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
//! #[contract]
//! pub struct Contract;
//! #[contractimpl]
//! impl Contract {
//! pub fn deploy(env: Env, wasm_hash: BytesN<32>) {
//! let salt = [0u8; 32];
//! let deployer = env.deployer().with_current_contract(salt);
//! // Deployed contract address is deterministic and can be accessed
//! // before deploying the contract.
//! let _ = deployer.deployed_address();
//! let contract_address = deployer.deploy(wasm_hash);
//! }
//! }
//! #[test]
//! fn test() {
//! # }
//! #
//! # #[cfg(feature = "testutils")]
//! # fn main() {
//! # let env = Env::default();
//! # let contract_address = env.register_contract(None, Contract);
//! # // Install the contract code before deploying its instance.
//! # let mock_wasm = [0u8; 0];
//! # let wasm_hash = env.deployer().upload_contract_wasm(mock_wasm.as_slice());
//! # ContractClient::new(&env, &contract_address).f(&wasm_hash);
//! let env = Env::default();
//! let contract_address = env.register_contract(None, Contract);
//! let contract = ContractClient::new(&env, &contract_address);
//! // Upload the contract code before deploying its instance.
//! let wasm_hash = env.deployer().upload_contract_wasm(DEPLOYED_WASM);
//! contract.deploy(&wasm_hash);
//! }
//! # #[cfg(not(feature = "testutils"))]
//! # fn main() { }
//! ```
//!
//! //! #### Deploy a contract with a multi-argument constructor
//!
//! ```
//! use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol, IntoVal};
//! const DEPLOYED_WASM_WITH_CTOR: &[u8] = include_bytes!("../doctest_fixtures/contract_with_constructor.wasm");
//! #[contract]
//! pub struct Contract;
//! #[contractimpl]
//! impl Contract {
//! pub fn deploy_with_constructor(env: Env, wasm_hash: BytesN<32>) {
//! let salt = [1u8; 32];
//! let deployer = env.deployer().with_current_contract(salt);
//! // Deployed contract address is deterministic and can be accessed
//! // before deploying the contract.
//! let _ = deployer.deployed_address();
//! let contract_address = deployer.deploy_with_constructor(
//! wasm_hash, (1_u32, 2_i64).into_val(&env));
//! }
//! }
//! #[test]
//! fn test() {
//! # }
//! # #[cfg(feature = "testutils")]
//! # fn main() {
//! let env = Env::default();
//! let contract_address = env.register_contract(None, Contract);
//! let contract = ContractClient::new(&env, &contract_address);
//! // Upload the contract code before deploying its instance.
//! let wasm_hash = env.deployer().upload_contract_wasm(DEPLOYED_WASM_WITH_CTOR);
//! contract.deploy_with_constructor(&wasm_hash);
//! }
//! # #[cfg(not(feature = "testutils"))]
//! # fn main() { }
//! ```

use crate::{
env::internal::Env as _, unwrap::UnwrapInfallible, Address, Bytes, BytesN, Env, IntoVal,
env::internal::Env as _, unwrap::UnwrapInfallible, Address, Bytes, BytesN, Env, IntoVal, Val,
Vec,
};

/// Deployer provides access to deploying contracts.
Expand Down Expand Up @@ -229,6 +269,31 @@ impl DeployerWithAddress {
.unwrap_infallible();
unsafe { Address::unchecked_new(env.clone(), address_obj) }
}

/// Deploy a contract that uses Wasm executable with provided hash.
///
/// `constructor_args` will be passed to the contract's constructor.
///
/// The address of the deployed contract is defined by the deployer address
/// and provided salt.
///
/// Returns the deployed contract's address.
pub fn deploy_with_constructor(
&self,
wasm_hash: impl IntoVal<Env, BytesN<32>>,
constructor_args: Vec<Val>,
) -> Address {
let env = &self.env;
let address_obj = env
.create_contract_with_constructor(
self.address.to_object(),
wasm_hash.into_val(env).to_object(),
self.salt.to_object(),
constructor_args.to_object(),
)
.unwrap_infallible();
unsafe { Address::unchecked_new(env.clone(), address_obj) }
}
}

pub struct DeployerWithAsset {
Expand Down
Loading

0 comments on commit 6ede50a

Please sign in to comment.