-
Notifications
You must be signed in to change notification settings - Fork 81
Updated the deployer example. #230
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -50,12 +50,18 @@ pub struct Deployer; | |||||||||
|
||||||||||
#[contractimpl] | ||||||||||
impl Deployer { | ||||||||||
/// Deploy the contract wasm 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: Bytes, init_fn: Symbol, init_args: Vec<RawVal>) -> (BytesN<32>, RawVal) { | ||||||||||
// Deploy the wasm. | ||||||||||
let id = env.deployer().with_current_contract(salt).deploy(wasm); | ||||||||||
/// 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 | ||||||||||
|
@@ -74,21 +80,35 @@ Contracts can deploy other contracts by using the SDK. | |||||||||
The contract ID of the deployed contract is deterministic and is derived from | ||||||||||
the deploying contract ID and the provided salt. | ||||||||||
|
||||||||||
Open the `deployer/deployer/src/src.rs` file to follow along. | ||||||||||
Open the `deployer/deployer/src/lib.rs` file to follow along. | ||||||||||
|
||||||||||
### Contract code installation | ||||||||||
|
||||||||||
Before deploying the new contract instances, the WASM code needs to be installed | ||||||||||
on-chain. Then it can be reused to deploy an arbitrary number of contract | ||||||||||
dmkozh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
instances. The installation typically should happen outside of the deployer | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
contract, as it needs to happen just once. | ||||||||||
while the deployer contract can be called multiple times. | ||||||||||
|
||||||||||
[Below](#tests) is the example of installing the contract code in tests. For the actual | ||||||||||
installation see the general deployment [tutorial](https://soroban.stellar.org/docs/tutorials/deploy-to-futurenet). | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||||||||||
|
||||||||||
### Deployer | ||||||||||
|
||||||||||
The `deployer` contract deploys other contracts. It accepts a salt that will be | ||||||||||
used to derive a contract ID, and a WASM bytes to deploy. It also accepts an | ||||||||||
initialization function and arguments to pass to that function. | ||||||||||
used to derive a contract ID, and a hash-based identifier of the installed WASM | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about removing this... Maybe we should really only hash the code (initially the install entries had more fields), but that would need to happen in the next release. Added an additional comment to make that clear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the wasm hash only a hash of the code or something else? Because If it is of something else I think the SDK is misleading. |
||||||||||
code to deploy. It also accepts an initialization function and arguments to pass | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rephrased. |
||||||||||
to that function. | ||||||||||
|
||||||||||
The contract first gets a deployer using the salt, then calls deploy with the | ||||||||||
WASM bytes. The WASM bytes will be deployed and the contract ID for that new | ||||||||||
contract is returned. The contract ID is deterministic and is derived from the | ||||||||||
deploying contract and the salt. | ||||||||||
WASM hash. The contract that with the code corresponding to the `wasm_hash` will | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rephrased and added more context regarding properties/benefits of hash-based deployment. |
||||||||||
be deployed and the contract ID for that new contract is returned. | ||||||||||
|
||||||||||
The contract ID is deterministic and is derived from the deploying contract and | ||||||||||
the salt. | ||||||||||
|
||||||||||
```rust | ||||||||||
let id = env.deployer().with_current_contract(salt).deploy(wasm); | ||||||||||
let id = env.deployer().with_current_contract(salt).deploy(wasm_hash); | ||||||||||
``` | ||||||||||
|
||||||||||
The contract invokes the new contract's intialization function and passes | ||||||||||
|
@@ -98,7 +118,8 @@ through the arguments. | |||||||||
let res: RawVal = env.invoke_contract(&id, &init_fn, init_args); | ||||||||||
``` | ||||||||||
|
||||||||||
The contract returns the new contract ID and the result of the initializatio function. | ||||||||||
The contract returns the new contract ID and the result of the initialization | ||||||||||
function. | ||||||||||
```rust | ||||||||||
(id, res) | ||||||||||
``` | ||||||||||
|
@@ -118,20 +139,16 @@ mod contract { | |||||||||
#[test] | ||||||||||
fn test() { | ||||||||||
let env = Env::default(); | ||||||||||
let deployer_id = BytesN::from_array(&env, &[0; 32]); | ||||||||||
env.register_contract(&deployer_id, Deployer); | ||||||||||
let client = DeployerClient::new(&env, &deployer_id); | ||||||||||
let client = DeployerClient::new(&env, &env.register_contract(None, Deployer)); | ||||||||||
|
||||||||||
// Install the WASM code to be deployed from the deployer contract. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||||||||||
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 wasm: Bytes = contract::WASM.into_val(&env); | ||||||||||
let init_fn = symbol!("init"); | ||||||||||
let init_fn_args = (5u32,).into_val(&env); | ||||||||||
let (contract_id, init_result) = client.deploy(&salt, &wasm, &init_fn, &init_fn_args); | ||||||||||
assert_eq!( | ||||||||||
contract_id, | ||||||||||
bytesn!(&env, 0xead19f55aec09bfcb555e09f230149ba7f72744a5fd639804ce1e934e8fe9c5d) | ||||||||||
); | ||||||||||
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. | ||||||||||
|
@@ -154,9 +171,7 @@ mod contract { | |||||||||
|
||||||||||
That contract contains the following code, which exports a couple | ||||||||||
functions that sets a value in the initialization function and allows the value | ||||||||||
to be retrieved from another function. The test contract will be used when | ||||||||||
testing the deployer, by deploying the contract and then checking if the | ||||||||||
contract is deployed by invoking its functions. | ||||||||||
to be retrieved from another function. | ||||||||||
|
||||||||||
```rust title="deployer/contract/src/lib.rs" | ||||||||||
pub struct Contract; | ||||||||||
|
@@ -174,51 +189,52 @@ impl Contract { | |||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
The test contract will be used when testing the deployer. The deployer contract | ||||||||||
deploys the test contract and invokes its `init` function. | ||||||||||
dmkozh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
In any test the first thing that is always required is an `Env`, which is the | ||||||||||
Soroban environment that the contract will run in. | ||||||||||
|
||||||||||
```rust | ||||||||||
let env = Env::default(); | ||||||||||
``` | ||||||||||
|
||||||||||
A fixed contract ID is used so that we can assert on the contract ID of the | ||||||||||
contract that gets deployed, which will be derived in part from the deployers | ||||||||||
contract ID. | ||||||||||
Install the code of the test contract that we have imported above via | ||||||||||
`contractimport!` and get the hash of the installed WASM code: | ||||||||||
|
||||||||||
```rust | ||||||||||
let deployer_id = BytesN::from_array(&env, &[0; 32]); | ||||||||||
// Install the WASM code to be deployed from the deployer contract. | ||||||||||
let wasm_hash = env.install_contract_wasm(contract::WASM); | ||||||||||
``` | ||||||||||
|
||||||||||
The deployer contract is registered with the environment. | ||||||||||
Register the deployer contract with the environment: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you keep these as sentences with trailing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||||||||||
|
||||||||||
```rust | ||||||||||
env.register_contract(&deployer_id, Deployer); | ||||||||||
``` | ||||||||||
|
||||||||||
...and created a client for it: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rephrased. |
||||||||||
|
||||||||||
```rust | ||||||||||
let client = DeployerClient::new(&env, &deployer_id); | ||||||||||
``` | ||||||||||
|
||||||||||
All public functions within an `impl` block that is annotated with the | ||||||||||
`#[contractimpl]` attribute have a corresponding function generated in a | ||||||||||
generated client type. The client type will be named the same as the contract | ||||||||||
type with `Client` appended. For example, in our contract the contract type is | ||||||||||
`Deployer`, and the client is named `DeployerClient`. | ||||||||||
|
||||||||||
```rust | ||||||||||
let client = DeployerClient::new(&env, &deployer_id); | ||||||||||
``` | ||||||||||
|
||||||||||
The client is used to invoke the `deploy` function. The contract will deploy the | ||||||||||
test contract WASM bytes, call the `"init"` function, and pass in a single | ||||||||||
`5u32` argument. | ||||||||||
test contract using the hash of its WASM code, call the `"init"` function, and | ||||||||||
pass in a single `5u32` argument. | ||||||||||
|
||||||||||
```rust | ||||||||||
// Deploy contract using deployer, and include an init function to call. | ||||||||||
let salt = Bytes::from_array(&env, &[0; 32]); | ||||||||||
let wasm: Bytes = contract::WASM.into_val(&env); | ||||||||||
let init_fn = symbol!("init"); | ||||||||||
let init_fn_args = (5u32,).into_val(&env); | ||||||||||
let (contract_id, init_result) = client.deploy(&salt, &wasm, &init_fn, &init_fn_args); | ||||||||||
assert_eq!( | ||||||||||
contract_id, | ||||||||||
bytesn!(&env, 0xead19f55aec09bfcb555e09f230149ba7f72744a5fd639804ce1e934e8fe9c5d) | ||||||||||
); | ||||||||||
let (contract_id, init_result) = client.deploy(&salt, &wasm_hash, &init_fn, &init_fn_args); | ||||||||||
assert!(init_result.is_void()); | ||||||||||
``` | ||||||||||
|
||||||||||
|
@@ -256,13 +272,16 @@ both contracts: | |||||||||
If you have [`soroban-cli`] installed, you can invoke the contract function to | ||||||||||
deploy the test contract. | ||||||||||
|
||||||||||
TODO: the CLI currently doesn't support the install operation. Before running | ||||||||||
the `invoke` below, the contract has to be installed. | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no plan to add an install command to the CLI in this iteration, so we need to remove this todo and come up with a way for them to "install" it. |
||||||||||
```sh | ||||||||||
soroban invoke \ | ||||||||||
--wasm target/wasm32-unknown-unknown/release/soroban_deployer_contract.wasm \ | ||||||||||
--id 0 \ | ||||||||||
--fn deploy \ | ||||||||||
--arg 0000000000000000000000000000000000000000000000000000000000000000 \ | ||||||||||
--arg $(xxd -p -c- target/wasm32-unknown-unknown/release/soroban_deployer_test_contract.wasm) \ | ||||||||||
--arg <installed WASM hash> \ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to tell people how to get this hash. Can you add that? |
||||||||||
--arg init \ | ||||||||||
--arg '[{"u32":5}]' | ||||||||||
``` | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Title needs capitalizing for consistency, and I suggest we shorten it, since it is easier to create a concept stories around single word names like "installation" and "deployment".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capitalized. I think we need to keep 'code' around though, as we are not installing the contract yet (IMO 'contract deploy' and 'contract install' aren't really distinguishable and both concern 'contracts', while the code is not a contract).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not really "code" either, it's compiled into some form of code, but not "code" the way developers think of code normally. And it is a contract. It's literally the contract. Deployment creates a contract instance, one of the paragraphs in the doc use that language of "contract instance". So I think we already have dual usage of the term. But that's fine, lets keep code as that's what is in the env and XDR too I realize.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WASM is binary code and that's the way I think about that personally. But I'm fine with updating the terminology to make it less ambiguous. I agree that the code is a contract in some sense, but OTOH saying 'contract instance' every time we refer to the thing you can invoke (and the thing that you'd use 99% of the time) is kind of redundant. Maybe they can be used interchangeably, but then we need something to disambiguate WASM in docs like this one.