This repository has been archived by the owner on Aug 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rework hello-world to use the soroban contract init command
- Loading branch information
1 parent
adc6515
commit a64d4ab
Showing
1 changed file
with
96 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,62 +23,62 @@ description: Create your first smart contract in Rust. | |
import Tabs from "@theme/Tabs"; | ||
import TabItem from "@theme/TabItem"; | ||
|
||
Once you've [setup](./setup.mdx) your development environment, you're ready to create your first Soroban contract. | ||
Once you've [set up](./setup.mdx) your development environment, you're ready to create your first Soroban contract. | ||
|
||
## Create New Project | ||
## Create a New Project | ||
|
||
Create a new Rust library using the `cargo new` command. | ||
Create a new project using the `init` command to create a `hello-world` project. | ||
|
||
```sh | ||
cargo new --lib hello-soroban | ||
```bash | ||
soroban contract init hello-world | ||
``` | ||
|
||
Open the `Cargo.toml`, it should look something like this: | ||
|
||
```toml title="Cargo.toml" | ||
[package] | ||
name = "hello-soroban" | ||
version = "0.1.0" | ||
edition = "2021" | ||
The `init` command will create a Rust workspace project, using the recommended structure for including Soroban contracts. Let’s take a look at the project structure: | ||
|
||
```text | ||
. | ||
├── Cargo.lock | ||
├── Cargo.toml | ||
├── README.md | ||
└── contracts | ||
└── hello_world | ||
├── Cargo.toml | ||
└── src | ||
├── lib.rs | ||
└── test.rs | ||
``` | ||
|
||
### Configure the Library Type | ||
|
||
Add the `crate-type` configuration, required for building contracts. | ||
### Cargo.toml | ||
|
||
```toml | ||
[lib] | ||
crate-type = ["cdylib"] | ||
``` | ||
The `Cargo.toml` file at the root of the project is set up as Rust Workspace, which allows us to include multiple Soroban contracts in one project. | ||
|
||
### Import `soroban-sdk` and Features | ||
#### Rust Workspace | ||
|
||
Add the following sections to the `Cargo.toml` to import the `soroban-sdk`, and configure a set of features explained below. | ||
The `Cargo.toml` file sets the workspace’s members as all contents of the `contracts` directory and sets the workspace’s `soroban-sdk` dependency version including the `testutils` feature, which will allow test utilities to be generated for calling the contract in tests. | ||
|
||
```toml | ||
[dependencies] | ||
soroban-sdk = "20.0.0" | ||
```toml title="Cargo.toml" | ||
[workspace] | ||
resolver = "2" | ||
members = [ | ||
"contracts/*", | ||
] | ||
|
||
[dev_dependencies] | ||
soroban-sdk = { version = "20.0.0", features = ["testutils"] } | ||
[workspace.dependencies] | ||
soroban-sdk = { version = "20.3.1" } | ||
|
||
[features] | ||
testutils = ["soroban-sdk/testutils"] | ||
``` | ||
|
||
The `features` list includes a `testutils` feature, which will cause additional test utilities to be generated for calling the contract in tests. | ||
|
||
:::info | ||
|
||
The `testutils` are automatically enabled inside [Rust unit tests] inside the same crate as your contract. If you write tests from another crate, you'll need to require the `testutils` feature for those tests and enable the `testutils` feature when running your tests with `cargo test --features testutils` to be able to use those test utilities. | ||
|
||
::: | ||
|
||
### Configure the `release` Profile | ||
#### `release` Profile | ||
|
||
Configuring the `release` profile to optimize the contract build is critical. Soroban contracts have a maximum size of 64KB. Rust programs, even small ones, without these configurations almost always exceed this size. | ||
|
||
Add the following to your `Cargo.toml` and use the `release` profile when building. | ||
The `Cargo.toml` file has the following release profile configured. | ||
|
||
```toml | ||
[profile.release] | ||
|
@@ -92,12 +92,10 @@ codegen-units = 1 | |
lto = true | ||
``` | ||
|
||
### Configure the `release-with-logs` Profile | ||
#### `release-with-logs` Profile | ||
|
||
Configuring a `release-with-logs` profile can be useful if you need to build a `.wasm` file that has logs enabled for printing debug logs when using the [`soroban-cli`]. Note that this is not necessary to access debug logs in tests or to use a step-through-debugger. | ||
|
||
Add the following to your `Cargo.toml` and use the `release-with-logs` profile when you need logs. | ||
|
||
```toml | ||
[profile.release-with-logs] | ||
inherits = "release" | ||
|
@@ -108,58 +106,58 @@ See the [logging example] for more information about how to log. | |
|
||
[logging example]: ../tutorials/logging.mdx | ||
|
||
### Wrapping it Up | ||
### Contracts Directory | ||
|
||
The steps above should produce a `Cargo.toml` that looks like so. | ||
The `contracts` directory is where Soroban contracts will live, each in their own directory. There is already a `hello_world` contract in there to get you started. | ||
|
||
```toml title="Cargo.toml" | ||
#### Contract-specific Cargo.toml file | ||
|
||
Each contract should have its own `Cargo.toml` file, which relies on the top-level `Cargo.toml` that we just discussed. | ||
|
||
This is where we can specify contract-specific package information. | ||
|
||
```toml | ||
[package] | ||
name = "project-name" | ||
version = "0.1.0" | ||
name = "hello-world" | ||
version = "0.0.0" | ||
authors = ["Stellar Development Foundation <[email protected]>"] | ||
license = "Apache-2.0" | ||
edition = "2021" | ||
rust-version = "1.74.0" | ||
publish = false | ||
``` | ||
|
||
The `crate-type` is configured to `cdylib` which is required for building contracts. | ||
|
||
```toml | ||
[lib] | ||
crate-type = ["cdylib"] | ||
doctest = false | ||
``` | ||
|
||
We also have included the soroban-sdk dependency, configured to use the version from the workspace Cargo.toml. | ||
|
||
```toml | ||
[dependencies] | ||
soroban-sdk = "20.0.0" | ||
soroban-sdk = { workspace = true } | ||
|
||
[dev_dependencies] | ||
soroban-sdk = { version = "20.0.0", features = ["testutils"] } | ||
|
||
[features] | ||
testutils = ["soroban-sdk/testutils"] | ||
|
||
[profile.release] | ||
opt-level = "z" | ||
overflow-checks = true | ||
debug = 0 | ||
strip = "symbols" | ||
debug-assertions = false | ||
panic = "abort" | ||
codegen-units = 1 | ||
lto = true | ||
|
||
[profile.release-with-logs] | ||
inherits = "release" | ||
debug-assertions = true | ||
soroban-sdk = { workspace = true, features = ["testutils"] } | ||
``` | ||
|
||
[Rust unit tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html | ||
[Rust integration tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html | ||
[`soroban-cli`]: setup.mdx#install-the-soroban-cli | ||
#### Contract Source Code | ||
|
||
## Write a Contract | ||
Creating a Soroban contract involves writing Rust code in the project’s `lib.rs` file. | ||
|
||
Once you've created a project, writing a contract involves writing Rust code in the project's `lib.rs` file. You can delete the default functions that cargo added to `lib.rs`. | ||
All contracts should begin with `#![no_std]` to ensure that the Rust standard library is not included in the build. The Rust standard library is large and not well suited to being deployed into small programs like those deployed to blockchains. | ||
|
||
All contracts should begin with `#![no_std]` to ensure that the Rust standard library is not included in the build. The Rust standard library is large and not well suited to being deployed into small programs like those deployed to blockchains. | ||
|
||
```rust | ||
#![no_std] | ||
``` | ||
|
||
The contract will need to import the types and macros that it needs from the `soroban-sdk` crate. Take a look at [Create a Project](#create-new-project) to see how to setup a project. | ||
The contract imports the types and macros that it needs from the `soroban-sdk` crate. | ||
|
||
```rust | ||
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec}; | ||
|
@@ -169,12 +167,16 @@ Many of the types available in typical Rust programs, such as `std::vec::Vec`, a | |
|
||
Contract inputs must not be references. | ||
|
||
The `#[contract]` attribute designates the Contract struct as the type to which contract functions are associated. This implies that the struct will have contract functions implemented for it. Contract functions are defined within an `impl` block for the struct, which is annotated with `#[contractimpl]`. It is important to note that contract functions should have names with a maximum length of 32 characters. Additionally, if a function is intended to be invoked from outside the contract, it should be marked with the `pub` visibility modifier. It is common for the first argument of a contract function to be of type `Env`, allowing access to a copy of the Soroban environment, which is typically necessary for various operations within the contract. | ||
The `#[contract]` attribute designates the Contract struct as the type to which contract functions are associated. This implies that the struct will have contract functions implemented for it. | ||
|
||
```rust | ||
#[contract] | ||
pub struct Contract; | ||
``` | ||
|
||
Contract functions are defined within an `impl` block for the struct, which is annotated with `#[contractimpl]`. It is important to note that contract functions should have names with a maximum length of 32 characters. Additionally, if a function is intended to be invoked from outside the contract, it should be marked with the `pub` visibility modifier. It is common for the first argument of a contract function to be of type `Env`, allowing access to a copy of the Soroban environment, which is typically necessary for various operations within the contract. | ||
|
||
```rust | ||
#[contractimpl] | ||
impl Contract { | ||
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> { | ||
|
@@ -183,37 +185,32 @@ impl Contract { | |
} | ||
``` | ||
|
||
Putting those pieces together a simple contract will look like this. | ||
Putting those pieces together a simple contract looks like this. | ||
|
||
```rust title="src/lib.rs" | ||
#![no_std] | ||
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec}; | ||
|
||
#[contract] | ||
pub struct Contract; | ||
pub struct HelloContract; | ||
|
||
#[contractimpl] | ||
impl Contract { | ||
/// Say Hello to someone or something. | ||
/// Returns a length-2 vector/array containing 'Hello' and then the value passed as `to`. | ||
impl HelloContract { | ||
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> { | ||
vec![&env, symbol_short!("Hello"), to] | ||
} | ||
} | ||
``` | ||
|
||
## Testing | ||
mod test; | ||
``` | ||
|
||
Writing tests for Soroban contracts involves writing Rust code using the test facilities and toolchain that you'd use for testing any Rust code. | ||
Note the `mod test` line at the bottom, this will tell Rust to compile and run the test code, which we’ll take a look at next. | ||
|
||
You'll need to add an annotation to bottom of `lib.rs` to tell Rust to compile and run the test code. | ||
#### Contract Unit Tests | ||
|
||
```rust | ||
#[cfg(test)] | ||
mod test; | ||
``` | ||
Writing tests for Soroban contracts involves writing Rust code using the test facilities and toolchain that you'd use for testing any Rust code. | ||
|
||
Given a simple contract like the contract demonstrated in the Write a Contract section, a simple test will look like this. | ||
Given our HelloContract, a simple test will look like this. | ||
|
||
<Tabs> | ||
<TabItem value="lib.rs" label="src/lib.rs"> | ||
|
@@ -223,30 +220,32 @@ Given a simple contract like the contract demonstrated in the Write a Contract s | |
use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec}; | ||
|
||
#[contract] | ||
pub struct Contract; | ||
pub struct HelloContract; | ||
|
||
#[contractimpl] | ||
impl Contract { | ||
impl HelloContract { | ||
pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> { | ||
vec![&env, symbol_short!("Hello"), to] | ||
} | ||
} | ||
#[cfg(test)] | ||
|
||
mod test; | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="test.rs" label="src/test.rs" default> | ||
|
||
```rust | ||
use crate::{Contract, ContractClient}; | ||
#![cfg(test)] | ||
|
||
use super::*; | ||
use soroban_sdk::{symbol_short, vec, Env}; | ||
|
||
#[test] | ||
fn hello() { | ||
fn test() { | ||
let env = Env::default(); | ||
let contract_id = env.register_contract(None, Contract); | ||
let client = ContractClient::new(&env, &contract_id); | ||
let contract_id = env.register_contract(None, HelloContract); | ||
let client = HelloContractClient::new(&env, &contract_id); | ||
|
||
let words = client.hello(&symbol_short!("Dev")); | ||
assert_eq!( | ||
|
@@ -287,9 +286,9 @@ assert_eq!( | |
); | ||
``` | ||
|
||
### Run the Tests | ||
## Run the Tests | ||
|
||
Run `cargo test` and watch the contract run. You should see the following output: | ||
Run `cargo test` and watch the unit test run. You should see the following output: | ||
|
||
```sh | ||
cargo test | ||
|
@@ -308,10 +307,7 @@ The first time you run the tests you may see output in the terminal of cargo com | |
|
||
::: | ||
|
||
[Rust unit test]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html | ||
[Rust integration test]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html | ||
|
||
## Build | ||
## Build the contract | ||
|
||
To build a Soroban contract to deploy or run, use the `soroban contract build` command. | ||
|
||
|
@@ -337,7 +333,7 @@ The `.wasm` file contains the logic of the contract, as well as the contract's [ | |
|
||
Use `soroban contract optimize` to further minimize the size of the `.wasm`. First, re-install soroban-cli with the `opt` feature: | ||
|
||
cargo install --locked --version 20.1.1 soroban-cli --features opt | ||
cargo install --locked --version 20.3.0 soroban-cli --features opt | ||
|
||
Then build an optimized `.wasm` file: | ||
|
||
|
@@ -347,30 +343,16 @@ Then build an optimized `.wasm` file: | |
This will optimize and output a new `hello_soroban.optimized.wasm` file in the same location as the input `.wasm`. | ||
|
||
:::tip | ||
Building optimized contracts is only necessary when deploying to a network with fees or when analyzing and profiling a contract to get it as small as possible. If you're just starting out writing a contract, these steps are not necessary. See [Build](#build) for details on how to build for development. | ||
::: | ||
|
||
## Commit to version control | ||
|
||
Before we go on to deploying the contract to Testnet in the next section, this is a great time to commit your code to version control. Even if you don't share your project with others, this will make it easier for you to see and understand your own changes throughout the rest of the tutorial. | ||
|
||
Cargo will have already setup the `hello-soroban` directory as a git repository. | ||
|
||
You should update the `.gitignore` file with `.soroban` directory as it will contain cached information about your local development and use of the `soroban-cli`: | ||
|
||
```bash | ||
echo .soroban >> .gitignore | ||
``` | ||
|
||
Now you can make your initial commit: | ||
Building optimized contracts is only necessary when deploying to a network with fees or when analyzing and profiling a contract to get it as small as possible. If you're just starting out writing a contract, these steps are not necessary. See [Build](#build) for details on how to build for development. | ||
|
||
```bash | ||
git add . | ||
git commit -m "Initial commit: hello-soroban contract" | ||
``` | ||
::: | ||
|
||
## Summary | ||
|
||
In this section, we wrote a simple contract that can be deployed to a Soroban network. | ||
|
||
Next we'll learn to deploy the hello-soroban contract to Soroban's Testnet network and interact with it over RPC using the CLI. | ||
Next we'll learn to deploy the HelloWorld contract to Soroban's Testnet network and interact with it over RPC using the CLI. | ||
|
||
[Rust unit tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html | ||
[`soroban-cli`]: setup.mdx#install-the-soroban-cli |