Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Rework hello-world to use the soroban contract init command
Browse files Browse the repository at this point in the history
  • Loading branch information
elizabethengelman committed Feb 16, 2024
1 parent adc6515 commit a64d4ab
Showing 1 changed file with 96 additions and 114 deletions.
210 changes: 96 additions & 114 deletions docs/getting-started/hello-world.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"
Expand All @@ -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};
Expand All @@ -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> {
Expand All @@ -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">
Expand All @@ -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!(
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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:

Expand All @@ -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

0 comments on commit a64d4ab

Please sign in to comment.