Skip to content

Commit

Permalink
Move book examples into standalone projects. Include them in book via…
Browse files Browse the repository at this point in the history
… mdbook preprocessor. (#766)

* Extract `hello_world` example from the book into a standalone project

Related to #544.
Follows the example set in #731.
Tested with `mdbook serve`.

Also adds the `hello_world` and `subcurrency` examples to the Sway
workspace so that we can test both projects under CI.

* Fix hello_world example project name in forc manifest

* Move fizzbuzz and wallet_smart_contract examples to standalone projects

* Add `build-all-examples` script and associated CI passes

This script walks the examples directory and, for every directory
containing a `forc.toml` file, runs `forc build` for that directory.
Examples that encounter a failure of some kind have their `stdout` and
`stderr` piped through so we can easily identify cause of failure in CI.

This also removes the examples from the workspace.

Currently unsure whether or not the added CI pass to install `forc` will
result in `forc` actually being available on the PATH in the CI worker,
but will find out soon.

Still need to work out how to run `forc test` for each project without
having cargo complain about building within the parent sway workspace.

* Add a clear build summary at the end of `build-all-examples`

Currently, it's tricky to get an idea of how many successes/failures
there were without combing through the stdout/stderr. This adds a little
summary to the end that looks something like this:

```
Build all examples summary:
  [✓]: /home/mindtree/programming/rust/fuel/sway/examples/fizzbuzz succeeded!
  [✓]: /home/mindtree/programming/rust/fuel/sway/examples/hello_world succeeded!
  [x]: /home/mindtree/programming/rust/fuel/sway/examples/wallet_smart_contract failed!
  [✓]: /home/mindtree/programming/rust/fuel/sway/examples/subcurrency succeeded!
3 successes, 1 failure
```

* Set build-all-examples script version to 0.0.0

* Exclude examples from workspace, Fix hello_world test harness path

* Temporarily comment out storage related code in wallet example

This example should be updated after #646 lands.

* Install forc with `--debug` to share build artifacts

Currently it takes CI another 7 mins to build and install forc from
scratch. This should allow `cargo` to re-use the artifacts it has
already built during the sway building and testing steps earlier in the
CI job.

* Change author field in examples from mindtree -> Fuel Labs
  • Loading branch information
mitchmindtree committed Feb 15, 2022
1 parent 951a38c commit e10acd6
Show file tree
Hide file tree
Showing 21 changed files with 359 additions and 143 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}

- name: Install forc
uses: actions-rs/cargo@v1
with:
command: install
args: --debug --path ./forc

- name: Build sway examples
uses: actions-rs/cargo@v1
with:
command: run
args: --bin build-all-examples

publish:
# Only do this job if publishing a release
needs: build
Expand Down
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"docstrings",
"examples/build-all-examples",
"forc",
"parser",
"sway-core",
Expand All @@ -11,7 +12,13 @@ members = [
"sway-types",
"sway-utils",
"test",
"test-sig-gen-util"
"test-sig-gen-util",
]
exclude = [
"examples/fizzbuzz",
"examples/hello_world",
"examples/subcurrency",
"examples/wallet_smart_contract",
]

[profile.dev.package.sway-server]
Expand Down
28 changes: 1 addition & 27 deletions docs/src/examples/fizzbuzz.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,5 @@ and receive back its fizzbuzzability as an enum. Note that the deserialization s
so the caller knows what to do with the bytes.

```sway
contract;
enum FizzBuzzResult {
Fizz: (),
Buzz: (),
FizzBuzz: (),
Other: u64,
}
abi FizzBuzz {
fn fizzbuzz(gas: u64, coins: u64, asset_id: b256, input: u64) -> FizzBuzzResult;
}
impl FizzBuzz for Contract {
fn fizzbuzz(gas: u64, coins: u64, asset_id: b256, input: u64) -> FizzBuzzResult {
if input % 15 == 0 {
FizzBuzzResult::FizzBuzz
} else if input % 3 == 0 {
FizzBuzzResult::Fizz
} else if input % 5 == 0 {
FizzBuzzResult::Buzz
} else {
FizzBuzzResult::Other(input)
}
}
}
{{#include ../../../examples/fizzbuzz/src/main.sw}}
```
37 changes: 1 addition & 36 deletions docs/src/examples/wallet_smart_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,5 @@
_Contract storage in the language syntax is a work-in-progress feature, and the following example does not currently compile._

```sway
contract;
use std::*;
const OWNER_ADDRESS: b256 = 0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861;
const ETH_ID: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;
storage {
balance: u64,
}
abi Wallet {
fn receive_funds(gas_to_forward: u64, coins_to_forward: u64, asset_id: b256, unused: ());
fn send_funds(gas_to_forward: u64, coins_to_forward: u64, asset_id: b256, req: SendFundsRequest);
}
impl Wallet for Contract {
fn receive_funds(gas_to_forward: u64, coins_to_forward: u64, asset_id: b256, unused: ()) {
if asset_id == ETH_ID {
let balance = storage.balance.write();
deref balance = balance + coins_to_forward;
};
}
fn send_funds(gas_to_forward: u64, coins_to_forward: u64, asset_id: b256, req: SendFundsRequest) {
assert(sender() == OWNER_ADDRESS);
assert(storage.balance > req.amount_to_send);
storage.balance = storage.balance - req.amount_to_send;
transfer_coins(asset_id, req.recipient_address, req.amount_to_send);
}
}
struct SendFundsRequest {
amount_to_send: u64,
recipient_address: b256,
}
{{#include ../../../examples/wallet_smart_contract/src/main.sw}}
```
90 changes: 12 additions & 78 deletions docs/src/getting-started/forc_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,7 @@ $ tree .
`Forc.toml` is the _manifest file_ (similar to `Cargo.toml` for Cargo or `package.json` for Node), and defines project metadata such as the project name and dependencies.

```toml
[project]
name = "hello_world"
author = "user"
entry = "main.sw"
license = "Apache-2.0"

[dependencies]
core = { git = "http://github.com/FuelLabs/sway-lib-core", version = "v0.0.1" }
std = { git = "http://github.com/FuelLabs/sway-lib-std", version = "v0.0.1" }
{{#include ../../../examples/hello_world/Forc.toml}}
```

Here are the contents of the only Sway file in the project, and the main entry point, `src/main.sw`:
Expand All @@ -39,7 +31,7 @@ Here are the contents of the only Sway file in the project, and the main entry p
script;
fn main() {
}
```

Expand Down Expand Up @@ -71,7 +63,14 @@ Bytecode size is 28 bytes.
[Return { id: ContractId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), val: 0, pc: 488, is: 464 }]
```

Use `forc json-abi` to output the ABI of the contract. To write this to a `.json` file (which is necessary for running tests below), pipe it using something like `forc json-abi > my_contract.json`. There is currently not a convention for where ABI files should be placed; one common choice is loose in the root directory.
Use `forc json-abi` to output the ABI of the contract. To write this to a `.json` file (which is necessary for running tests below), pipe it using something like:

```console
forc json-abi > my-contract-abi.json
```

There is currently not a convention for where ABI files should be placed; one
common choice is loose in the root directory.

## Testing a Sway Project with Forc

Expand All @@ -96,78 +95,13 @@ These tests can be run using either `cargo test`, or `forc test` which will look
For example, let's write tests against the following contract, written in Sway. This can be done in the pregenerated `src/main.sw` or in a new file in `src`. In the case of the latter, update the `entry` field in `Forc.toml` to point at the new contract.

```sway
contract;
use std::storage::*;
use std::constants::*;
abi TestContract {
fn initialize_counter(gas_: u64, amount_: u64, coin_: b256, value: u64) -> u64;
fn increment_counter(gas_: u64, amount_: u64, coin_: b256, amount: u64) -> u64;
}
const SLOT = 0x0000000000000000000000000000000000000000000000000000000000000000;
impl TestContract for Contract {
fn initialize_counter(gas_: u64, amount_: u64, color_: b256, value: u64) -> u64 {
store(SLOT, value);
value
}
fn increment_counter(gas_: u64, amount_: u64, color_: b256, amount: u64) -> u64 {
let storedVal: u64 = get(SLOT);
let value = storedVal + amount;
store(SLOT, value);
value
}
}
{{#include ../../../examples/hello_world/src/main.sw}}
```

Our `tests/harness.rs` file could look like:

```rust
use fuel_tx::Salt;
use fuels_abigen_macro::abigen;
use fuels_contract::contract::Contract;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};

// Generate Rust bindings from our contract JSON ABI
abigen!(MyContract, "./my-contract-abi.json");

#[tokio::test]
async fn harness() {
let rng = &mut StdRng::seed_from_u64(2322u64);

// Build the contract
let salt: [u8; 32] = rng.gen();
let salt = Salt::from(salt);
let compiled = Contract::compile_sway_contract("./", salt).unwrap();

// Launch a local network and deploy the contract
let (client, contract_id) = Contract::launch_and_deploy(&compiled).await.unwrap();

let contract_instance = MyContract::new(compiled, client);

// Call `initialize_counter()` method in our deployed contract.
// Note that, here, you get type-safety for free!
let result = contract_instance
.initialize_counter(42)
.call()
.await
.unwrap();

assert_eq!(42, result);

// Call `increment_counter()` method in our deployed contract.
let result = contract_instance
.increment_counter(10)
.call()
.await
.unwrap();

assert_eq!(52, result);
}
{{#include ../../../examples/hello_world/tests/harness.rs}}
```

Then, in the root of our project, running `forc test` or `cargo test` will run the test above, compiling and deploying the contract to a local Fuel network, and calling the ABI methods against the contract deployed in there:
Expand Down
6 changes: 6 additions & 0 deletions examples/build-all-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "build-all-examples"
description = "Runs `forc build` for all projects under the Sway `examples` directory."
version = "0.0.0"
edition = "2021"
publish = false
88 changes: 88 additions & 0 deletions examples/build-all-examples/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Runs `forc build` for all projects under the Sway `examples` directory.
//!
//! NOTE: This expects both `forc` and `cargo` to be available in `PATH`.

use std::{
fs,
io::{self, Write},
path::{Path, PathBuf},
};

fn main() {
let proj_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let examples_dir = proj_dir
.parent()
.expect("failed to find examples directory");

// Track discovered projects and whether or not they were successful.
let mut summary: Vec<(PathBuf, bool)> = vec![];

for res in fs::read_dir(examples_dir).expect("failed to walk examples directory") {
let entry = match res {
Ok(entry) => entry,
_ => continue,
};
let path = entry.path();
if !path.is_dir() || !dir_contains_forc_manifest(&path) {
continue;
}

let output = std::process::Command::new("forc")
.args(["build", "--path"])
.arg(&path)
.output()
.expect("failed to run `forc build` for example project");

// Print output on failure so we can read it in CI.
let success = if !output.status.success() {
io::stdout().write_all(&output.stdout).unwrap();
io::stdout().write_all(&output.stderr).unwrap();
false
} else {
true
};

summary.push((path, success));
}

println!("\nBuild all examples summary:");
let mut successes = 0;
for (path, success) in &summary {
let (checkmark, status) = if *success {
("[✓]", "succeeded")
} else {
("[x]", "failed")
};
println!(" {}: {} {}!", checkmark, path.display(), status);
if *success {
successes += 1;
}
}
let failures = summary.len() - successes;
let successes_str = if successes == 1 {
"success"
} else {
"successes"
};
let failures_str = if failures == 1 { "failure" } else { "failures" };
println!(
"{} {}, {} {}",
successes, successes_str, failures, failures_str
);

if failures > 0 {
std::process::exit(1);
}
}

// Check if the given directory contains `Forc.toml` at its root.
fn dir_contains_forc_manifest(path: &Path) -> bool {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.flatten() {
if entry.path().file_name().and_then(|s| s.to_str()) == Some("Forc.toml") {
return true;
}
}
}
false
}
20 changes: 20 additions & 0 deletions examples/fizzbuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
authors = ["Fuel Labs <contact@fuel.sh>"]
edition = "2021"
license = "Apache-2.0"
name = "fizzbuzz"
version = "0.1.0"

[dependencies]
fuel-gql-client = { version = "0.2", default-features = false }
fuel-tx = "0.3"
fuels-abigen-macro = "0.3"
fuels-contract = "0.3"
fuels-core = "0.3"
rand = "0.8"
tokio = { version = "1.12", features = ["rt", "macros"] }

[[test]]
harness = true
name = "integration_tests"
path = "tests/harness.rs"
9 changes: 9 additions & 0 deletions examples/fizzbuzz/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
author = "Fuel Labs <contact@fuel.sh>"
entry = "main.sw"
license = "Apache-2.0"
name = "fizzbuzz"

[dependencies]
core = { git = "http://github.com/FuelLabs/sway-lib-core" }
std = { git = "http://github.com/FuelLabs/sway-lib-std" }
26 changes: 26 additions & 0 deletions examples/fizzbuzz/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
contract;

enum FizzBuzzResult {
Fizz: (),
Buzz: (),
FizzBuzz: (),
Other: u64,
}

abi FizzBuzz {
fn fizzbuzz(gas: u64, coins: u64, asset_id: b256, input: u64) -> FizzBuzzResult;
}

impl FizzBuzz for Contract {
fn fizzbuzz(gas: u64, coins: u64, asset_id: b256, input: u64) -> FizzBuzzResult {
if input % 15 == 0 {
FizzBuzzResult::FizzBuzz
} else if input % 3 == 0 {
FizzBuzzResult::Fizz
} else if input % 5 == 0 {
FizzBuzzResult::Buzz
} else {
FizzBuzzResult::Other(input)
}
}
}
4 changes: 4 additions & 0 deletions examples/fizzbuzz/tests/harness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[tokio::test]
async fn harness() {
assert_eq!(true, true);
}
Loading

0 comments on commit e10acd6

Please sign in to comment.