Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions website/docs/tutorial/01-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,15 @@ In this case `#[contract]` is an [attribute macro](https://doc.rust-lang.org/ref
Here we're defining a [struct](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) (a "structure" to hold values) and applying attributes of a Stellar smart contract. A `struct` also allows defining methods. In this case the structs holds no values but we will still define methods on it.

```rust
const THE_NUMBER: Symbol = symbol_short!("n");
const THE_NUMBER: &Symbol = &symbol_short!("n");
pub const ADMIN_KEY: &Symbol = &symbol_short!("ADMIN");
Comment thread
elizabethengelman marked this conversation as resolved.
Outdated
```

Now the most important part of our contract: the number! This line creates a key for storing and retrieving contract data. A `Symbol` is a short string type (max 32 characters) that is more optimized for use on the blockchain. And we're using the `symbol_short` macro for an even smaller key (max 9 characters). As a contract author, you want to use tricks like this to lower costs as much as you can.

The second line creates a key for storing the address of this contract's administrator. It's almost the same code as storing our number, but uses the `&` which is called a [reference](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html). Instead of the value, it's a pointer to where the value lives.
The second line creates a key for storing the address of this contract's administrator.

Both of these keys use the `&` which is called a [reference](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html). Instead of the value, it's a pointer to where the value lives.

```rust
#[contractimpl]
Expand All @@ -241,7 +243,7 @@ impl GuessTheNumber {
Let's `impl`ement our contract's functionality.

```rust
pub fn __constructor(env: &Env, admin: &Address) {
pub fn __constructor(env: &Env, admin: Address) {
Self::set_admin(env, admin);
}
```
Expand All @@ -253,20 +255,20 @@ A contract's `constructor` runs when it is deployed. In this case, we're saying
pub fn reset(env: &Env) {
Self::require_admin(env);
let new_number: u64 = env.prng().gen_range(1..=10);
env.storage().instance().set(&THE_NUMBER, &new_number);
env.storage().instance().set(THE_NUMBER, &new_number);
}
```

And here is the reset function. Note that we use `require_admin()` here so only you can run this function. It generates a random number between 1 and 10 and uses our key to store it.

```rust
/// Guess a number from 1 to 10
/// Guess a number between 1 and 10
pub fn guess(env: &Env, a_number: u64) -> bool {
a_number
== env
.storage()
.instance()
.get::<_, u64>(&THE_NUMBER)
.get::<_, u64>(THE_NUMBER)
.expect("no number set")
}
```
Expand All @@ -277,7 +279,7 @@ Finally, we add the `guess` function which accepts a number as the guess and com
mod test;
```

This last line includes the test module into this file. It's handy to write unit tests for our code in a separate file (`contracts/guess-the-number/src/test.rs`), but you could also write them inline if you want by defining the module. Note you also need to tell the compile that this is a test module, which is at the top of our file `#![cfg(test)]`.
This line makes sure to include the test module in the file. It's handy to write unit tests for our code in a separate file (`contracts/guess-the-number/src/test.rs`), but you could also write them inline if you want by defining the module. Note you also need to tell the compile that this is a test module, which is at the top of our file `#![cfg(test)]`.

```rust
#[cfg(test)]
Expand All @@ -297,7 +299,7 @@ The docstring for our `guess` function says to guess a number "between 1 and 10"
pub fn guess(env: &Env, a_number: u64) -> bool {
```

Save the file and watch your terminal output. The contracts get rebuilt, redeployed, and clients for them get regenerated for your frontend. Then Vite hot-reloads your app and you should see the change in the contract explorer in your browser.
Save the file and watch your terminal output. The contracts get rebuilt, redeployed, and clients for them get regenerated for your frontend. Then Vite hot-reloads your app and you should see the change in the Debugger in your browser.

Tada!

Expand All @@ -324,8 +326,11 @@ We're storing some state for tracking the input's value and whether the guess wa

```ts
const submitGuess = async () => {
if (!theGuess) return;
const { result } = await game.guess({ a_number: BigInt(theGuess) });
if (!theGuess || !address) return;
const { result } = await game.guess({
a_number: BigInt(theGuess),
guesser: address,
});
setGuessedIt(result);
};
```
Expand Down
31 changes: 16 additions & 15 deletions website/docs/tutorial/02-making-improvements.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Let's walk through this line by line:
1. The Wasm gets uploaded to the blockchain, so that many contracts could use it.
2. A contract gets deployed (aka "instantiated", in the current parlance of this output) so that there is an actual smart contract that refers to, or points to, that Wasm.

- `after_deploy`: calls to the contract to make after it gets deployed. Kind of like the `constructor_args`, these are specified using _only_ the part that comes after the `--`. The setting above tells Scaffold CLI to make the following call, after deploying the contract:
- `after_deploy`: method calls to make to the contract after it gets deployed. Kind of like the `constructor_args`, but, these are specified using _only_ the part that comes after the `--`. The setting above tells Scaffold CLI to make call the `reset` method, after deploying the contract:

```bash
stellar contract deploy \
Expand Down Expand Up @@ -192,9 +192,9 @@ If you already tried re-running the `guess` logic in the app, you'll see...

Nothing. Nothing happens. At least not yet.

The contract didn't change, so Scaffold CLI didn't re-deploy the contract. You're still using the one that had the `reset` method called right after deploy.
The contract didn't change, so Scaffold CLI didn't re-deploy the contract. You're still using the instance that had the `reset` method called right after deploy.

Once [theahaco/scaffold-stellar#259](https://github.com/theahaco/scaffold-stellar/issues/259) is complete, you will be able to run `stellar scaffold reset`. Until then, you can remove the alias that Scaffold Stellar uses to keep track of this contract. Stop the `npm run start` process, then run:
Once [theahaco/scaffold-stellar#259](https://github.com/theahaco/scaffold-stellar/issues/259) is complete, you will be able to run `stellar scaffold reset`. Until then, you can remove the alias that Scaffold Stellar uses to keep track of this contract, which allows us to re-deploy a new `guess_the_number` contract. Stop the `npm run start` process, then run:

```bash
stellar contract alias remove guess_the_number --network local
Expand Down Expand Up @@ -242,7 +242,7 @@ impl GuessTheNumber {
/// Private helper function to generate and store a new random number
fn set_random_number(env: &Env) {
let new_number: u64 = env.prng().gen_range(1..=10);
env.storage().instance().set(&THE_NUMBER, &new_number);
env.storage().instance().set(THE_NUMBER, &new_number);
}
}
```
Expand Down Expand Up @@ -291,7 +291,7 @@ Much cleaner! The logic is now centralized in our helper function. Note that thi
$ npm start
```

Click over to `&lt;/&gt; Debugger` if you're not there already and select the `guess_the_number` contract. You'll see that `reset` is listed here, but `set_random_number` is not.
Click over to the Debugger if you're not there already and select the `guess_the_number` contract. You'll see that `reset` is listed here, but `set_random_number` is not.

Our `reset` method is available to be called by code _outside_ our contract because we opted in to it being a public method with the `pub` keyword. Our `set_random_number` is private by default, it's not visible to the outside world. It's not listed in the Contract Explorer. It's not listed in the CLI help either:

Expand All @@ -315,7 +315,7 @@ error: unrecognized subcommand 'set_random_number'

Nope! Just because we made it public, we still require authentication so only admins can call it. Rust's idea of public vs private handles "where" the functions can be called. You still need to handle "who" calls it. That's why we set the contract admin in it's constructor method and check it with `Self::require_admin(env);`.

You can try this out by invoking it from the Contract Explorer in your browser. The admin is `me`, but you didn't import that account into your browser wallet. Go ahead and hit `Submit` on the `reset` function.
You can try this out by invoking it from the Debugger in your browser. The admin is `me`, but you didn't import that account into your browser wallet. Go ahead and hit `Submit` on the `reset` function. You should see the transaction fail.

You could also try this out in the CLI. Create a non-admin identity to see how it fails:

Expand Down Expand Up @@ -383,12 +383,13 @@ use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Sy
#[contract]
pub struct GuessTheNumber;

const THE_NUMBER: Symbol = symbol_short!("n");
const THE_NUMBER: &Symbol = &symbol_short!("n");
pub const ADMIN_KEY: &Symbol = &symbol_short!("ADMIN");

#[contractimpl]
impl GuessTheNumber {
pub fn __constructor(env: &Env, admin: &Address) {
/// Constructor to initialize the contract with an admin and a random number
pub fn __constructor(env: &Env, admin: Address) {
Self::set_admin(env, admin);
Self::set_random_number(env);
}
Expand All @@ -399,22 +400,22 @@ impl GuessTheNumber {
Self::set_random_number(env);
}

/// Guess a number between 1 and 10
/// Guess a number between 1 and 10, inclusive
pub fn guess(env: &Env, a_number: u64) -> bool {
a_number == Self::number(env)
}

/// Private helper function to generate and store a new random number
fn set_random_number(env: &Env) {
let new_number: u64 = env.prng().gen_range(1..=10);
env.storage().instance().set(&THE_NUMBER, &new_number);
env.storage().instance().set(THE_NUMBER, &new_number);
}

/// readonly function to get the current number
fn number(env: &Env) -> u64 {
// We can unwrap because the number is set in the constructor
// and then only reset by the admin
unsafe { env.storage().instance().get(THE_NUMBER).unwrap_unchecked() }
unsafe { env.storage().instance().get::<_, u64>(THE_NUMBER).unwrap_unchecked() }
}

/// Upgrade the contract to new wasm. Only callable by admin.
Expand All @@ -429,12 +430,12 @@ impl GuessTheNumber {
}

/// Set a new admin. Only callable by admin.
fn set_admin(env: &Env, admin: &Address) {
fn set_admin(env: &Env, admin: Address) {
// Check if admin is already set
if env.storage().instance().has(ADMIN_KEY) {
panic!("admin already set");
}
env.storage().instance().set(ADMIN_KEY, admin);
env.storage().instance().set(ADMIN_KEY, &admin);
}

/// Private helper function to require auth from the admin
Expand All @@ -454,7 +455,7 @@ Let's test that our improvements work. You should still have the `npm start` pro
1. `stellar scaffold watch --build-clients`: watches for any changes in your `contracts/` folders, then rebuilds and redeploys them
2. `vite`: watches for any changes in your `src/` folder and hot-reloads the UI

That means any time you add a method, tweak arguments, or even add documentation, everything is immediately reflected on the local network, your application in the browser, and in the Contract Explorer. Let's add some info to the `guess` method's documentation:
That means any time you add a method, tweak arguments, or even add documentation, everything is immediately reflected on the local network, your application in the browser, and in the Debugger. Let's add some info to the `guess` method's documentation:

```rust
/// Guess a number between 1 and 10, inclusive. Returns a boolean.
Expand All @@ -465,7 +466,7 @@ As soon as you hit save, watch the Contract Explorer reload with the new text. N

### How to Write Unit Tests

TODO
_🏗️✨ Coming soon._

## What We've Learned

Expand Down
22 changes: 8 additions & 14 deletions website/docs/tutorial/03-adding-payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This creates real stakes and makes the game much more engaging!

## Step 1: 🪙 Add Asset Import

First, we need the `import_asset` macro from Stellar Registry. Add the following to your imports at the top of `lib.rs`:
First, we need the `import_asset` macro from Stellar Registry. Add the following to your imports at the top of your contract `lib.rs` file:

```diff
#![no_std]
Expand All @@ -34,7 +34,7 @@ First, we need the `import_asset` macro from Stellar Registry. Add the following
+import_asset!(xlm);
```

Stellar Registry integrates with Scaffold Stellar, giving names & versions to contracts & contract Wasms. It also provides helpers like `import_asset` to make it easier to work with [Stellar Asset Contracts](https://developers.stellar.org/docs/tokens/stellar-asset-contract).
Stellar Registry integrates with Scaffold Stellar, giving names and versions to contracts and contract Wasms. It also provides helpers like `import_asset` to make it easier to work with [Stellar Asset Contracts](https://developers.stellar.org/docs/tokens/stellar-asset-contract).

## Step 2: 💰 Add Funds to the Contract

Expand All @@ -46,9 +46,9 @@ Whenever the admin resets the number, we need to transfer some funds to the cont
env.storage().instance().set(&THE_NUMBER, &new_number);

// Seed the initial pot
let x = xlm::client(env);
let x = xlm::token_client(env);
let admin = Self::admin(env).expect("admin not set");
x.transfer(10_000_000_0, &admin, env.current_contract_address());
x.transfer(&admin, env.current_contract_address(), &10_000_000_0);
}
```

Expand All @@ -75,21 +75,15 @@ pub fn guess(env: &Env, guesser: Address, a_number: u64) -> bool {
}

// pay full pot to `guesser`, whether they sent the transaction or not
let tx = xlm_client.transfer(
xlm_client.transfer(
&guesser,
env.current_contract_address(),
guesser,
xlm_client.balance(env.current_contract_address()),
&xlm_client.balance(&env.current_contract_address()),
);
if tx.is_err() {
panic!("transfer failed!");
}
} else {
// Before transferring their funds, make sure guesser is actually the one calling this function
guesser.require_auth();
let tx = xlm_client.transfer(guesser, env.current_contract_address(), 1_000_000_0);
if tx.is_err() {
panic!("transfer failed!");
}
xlm_client.transfer(&guesser, &env.current_contract_address(), &1_000_000_0);
}

guessed_it
Expand Down