Hi there! Welcome to my ninth post of my series called "Soroban Contracts 101", where I'll be explaining the basics of Soroban contracts, such as data storage, authentication, custom types, and more. All the code that we're gonna explain throughout this series will mostly come from soroban-contracts-101 github repository.
In this ninth post of the series, I'll be covering soroban deployer. Deployer is a contract that can deploy and initialize other contracts. Some key benefits of using a deployer contract are:
Abstraction - the deployment logic is separated from main contracts, which can simplify their design
Privileged deployment - only designated deployer accounts/contracts can deploy other contracts, which can be important for security
Standardization - having a common deployer contract interface could allow interoperable deployment of contracts from various sources
We're gonna using 2 contract, Deployer
contract act as the "contract deployer", and Contract
contract act as the "test contract" that are going to be deployed by our Deployer
contract.
The Contracts Code
Test Contract Code
pub struct Contract;
const KEY: Symbol = symbol!("value");
#[contractimpl]
impl Contract {
pub fn init(env: Env, value: u32) {
env.storage().set(KEY, value);
}
pub fn value(env: Env) -> u32 {
env.storage().get_unchecked(KEY).unwrap()
}
}
Our Test Contract code defines a simple contract that:
- Stores a value at a constant
KEY
ininit
- Exposes a
value
function to retrieve the stored value
This is a very basic contract that shows storing and retrieving a value from contract storage.
Deployer Contract Code
pub struct Deployer;
#[contractimpl]
impl Deployer {
/// Deploy the contract using WASM hash and after deployment invoke the init
/// function of the contract with the given arguments. Returns the
/// contract ID and result of the init function.
pub fn deploy(
env: Env,
salt: Bytes,
wasm_hash: BytesN<32>,
init_fn: Symbol,
init_args: Vec<RawVal>,
) -> (BytesN<32>, RawVal) {
// Deploy the contract using the installed WASM code with given hash.
let id = env.deployer().with_current_contract(salt).deploy(wasm_hash);
// Invoke the init function with the given arguments.
let res: RawVal = env.invoke_contract(&id, &init_fn, init_args);
// Return the contract ID of the deployed contract and the result of
// invoking the init result.
(id, res)
}
}
Our deployer contract can deploy and initialize other contracts. It:
- Defines a
Deployer
struct - Implements a
contractimpl
onDeployer
- Defines a
deploy
function that:
- Takes various input parameters (salt, WASM hash, init function name, init arguments)
- Uses the Env's deployer to deploy a contract from the given WASM hash
- Calls the
init
function on the deployed contract, passing in the init arguments- Returns the contract ID and init result
The Deployer Contract Test Code
// The contract that will be deployed by the deployer contract.
mod contract {
soroban_sdk::contractimport!(
file = "../../target/wasm32-unknown-unknown/release/soroban_deployer_test_contract.wasm"
);
}
#[test]
fn test() {
let env = Env::default();
let client = DeployerClient::new(&env, &env.register_contract(None, Deployer));
// Install the WASM that will be deployed by the deployer contract.
let wasm_hash = env.install_contract_wasm(contract::WASM);
// Deploy contract using deployer, and include an init function to call.
let salt = Bytes::from_array(&env, &[0; 32]);
let init_fn = symbol!("init");
let init_fn_args = (5u32,).into_val(&env);
let (contract_id, init_result) = client.deploy(&salt, &wasm_hash, &init_fn, &init_fn_args);
assert!(init_result.is_void());
// Invoke contract to check that it is initialized.
let client = contract::Client::new(&env, &contract_id);
let sum = client.value();
assert_eq!(sum, 5);
}
Our test code here exercises deploying and initializing a contract using Deployer
contract. It:
- Creates an
Env
- Registers the
Deployer
contract - Creates a Deployer client
- Installs the WASM for the deployed
Contract
contract - Calls
deploy
on the Deployer, passing in:
- Salt
- TestDeployer WASM hash
- init function name
- init argument (5)
- Asserts that the
init
returns () (is void) - Creates a
contract
client and verifies that it returns the expected value (5), showing it was properly initialized
So this test thoroughly exercises the deployer/deployed contract scheme, and verifies that the deployed contract was initialized as expected.
Running Contract Tests
Test conducted in deployer directory
cd deployer/deployer
cargo test
If the tests are successful, you should see an output similar to:
running 1 test
test test::test ... ok
Building The Contract
We need to build both Deployer
contract and Contract
contract, to build Deployer contract use the following command:
cd .../deployer/deployer
cargo build --target wasm32-unknown-unknown --release
To build Contract contract use the following command:
cd .../deployer/contract
cargo build --target wasm32-unknown-unknown --release
Both .wasm files should be found in the ../target directory:
../target/wasm32-unknown-unknown/release/soroban_deployer_contract.wasm
../target/wasm32-unknown-unknown/release/soroban_deployer_test_contract.wasm
Initializing The Contract
contract
Before a contract can be deployed from its WASM code, the WASM must be installed on the blockchain. The WASM installation happens outside of the deployer contract, and only needs to occur once.
After the WASM is installed, the deployer contract can deploy any number of instances of the contract from that single installed WASM. To initialize our Contract
contract use the following command :
soroban contract install --wasm target/wasm32-unknown-unknown/release/soroban_deployer_test_contract.wasm
Returning our WASM hash, in my case :
77c33183640b62da4a65c453047d29c846b6aace87ee64a8f532623388b6b06e
Invoking The Contracts
We will use our WASM hash value from previous step,passing it to deploy
function from Deployer
contract. The command will be :
soroban contract invoke \
--wasm target/wasm32-unknown-unknown/release/soroban_deployer_contract.wasm \
--id 1 \
-- \
deploy \
--salt 0000000000000000000000000000000000000000000000000000000000000000 \
--wasm_hash 77c33183640b62da4a65c453047d29c846b6aace87ee64a8f532623388b6b06e\
--init_fn init \
--init_args '[{"u32":10}]'
Above command will return contract id of our deployed Contract
contract, in my case :
["ef370e4a3694cde1525f48d7c7666dc71da119aee5b446b0cbd1a274069ab8c8",null]
Then we will invoke our Contract
contract using this following command :
soroban contract invoke \
--id ef370e4a3694cde1525f48d7c7666dc71da119aee5b446b0cbd1a274069ab8c8\
--fn value
You should see the following output:
10
Conclusion
That's it! We explored the deployer contract in Soroban. The key point is the WASM hash for the deployed contract need to installed once before deploying it using the Deployer
contract. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.
Top comments (0)