Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 20 additions & 6 deletions docusaurus/docs/backends/03-casper.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ There would be no point in writing a contract if you couldn't deploy it to the b
You can do it in two ways: provided by the Casper itself: using the `casper-client` tool
or using the Odra's Livenet integration.

:::note
In the commands below, we use `casper-client` version 5.0.0.
:::

Let's explore the first option to better understand the process.

:::note
Expand All @@ -104,6 +108,7 @@ Every contract written in Odra expects those arguments to be set:
- `odra_cfg_package_hash_key_name` - `String` type. The key under which the package hash of the contract will be stored.
- `odra_cfg_allow_key_override` - `Bool` type. If `true` and the key specified in `odra_cfg_package_hash_key_name` already exists, it will be overwritten.
- `odra_cfg_is_upgradable` - `Bool` type. If `true`, the contract will be deployed as upgradable.
- `odra_cfg_is_upgrade` - `Bool` type. If `true`, the contract will be upgraded. If we want to install a contract to should be set to `false`.

Additionally, if required by the contract, you can pass constructor arguments.

Expand All @@ -117,15 +122,18 @@ To deploy your contract with a constructor using `casper-client`, you need to pa
Additionally, you need to pass the `value` argument, which sets the arbitrary initial value for the counter.

```bash
casper-client put-deploy \
casper-client put-transaction session \
--node-address [NODE_ADDRESS] \
--chain-name casper-test \
--secret-key [PATH_TO_YOUR_KEY]/secret_key.pem \
--payment-amount 5000000000000 \
--session-path ./wasm/counter.wasm \
--gas-price-tolerance 1 \
--standard-payment true \
--wasm-path ./wasm/counter.wasm \
--session-arg "odra_cfg_package_hash_key_name:string:'counter_package_hash'" \
--session-arg "odra_cfg_allow_key_override:bool:'true'" \
--session-arg "odra_cfg_is_upgradable:bool:'true'" \
--session-arg "odra_cfg_is_upgrade:bool:'false'" \
--session-arg "value:u32:42"
```

Expand All @@ -145,15 +153,18 @@ It produces the `erc721_token.wasm` file in the `wasm` directory.

Now it's time to deploy the contract.
```bash
casper-client put-deploy \
casper-client put-transaction session \
--node-address [NODE_ADDRESS] \
--chain-name casper-test \
--secret-key [PATH_TO_YOUR_KEY]/secret_key.pem \
--payment-amount 300000000000 \
--session-path ./wasm/erc721_token.wasm \
--gas-price-tolerance 1 \
--standard-payment true \
--wasm-path ./wasm/erc721_token.wasm \
--session-arg "odra_cfg_package_hash_key_name:string:'my_nft'" \
--session-arg "odra_cfg_allow_key_override:bool:'false'" \
--session-arg "odra_cfg_is_upgradable:bool:'true'" \
--session-arg "odra_cfg_is_upgrade:bool:'false'" \
--session-arg "name:string:'MyNFT'" \
--session-arg "symbol:string:'NFT'" \
--session-arg "base_uri:string:'https://example.com/'"
Expand All @@ -178,15 +189,18 @@ cargo odra build -b casper -c erc1155_token

Contract deployment:
```bash
casper-client put-deploy \
casper-client put-transaction session \
--node-address [NODE_ADDRESS] \
--chain-name casper-test \
--secret-key [PATH_TO_YOUR_KEY]/secret_key.pem \
--payment-amount 300000000000 \
--session-path ./wasm/erc1155_token.wasm \
--gas-price-tolerance 1 \
--standard-payment true \
--wasm-path ./wasm/erc1155_token.wasm \
--session-arg "odra_cfg_package_hash_key_name:string:'my_tokens'" \
--session-arg "odra_cfg_allow_key_override:bool:'false'" \
--session-arg "odra_cfg_is_upgradable:bool:'true'" \
--session-arg "odra_cfg_is_upgrade:bool:'false'" \
--session-arg "odra_cfg_constructor:string:'init'" \
```

Expand Down
1 change: 1 addition & 0 deletions docusaurus/docs/tutorials/build-deploy-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ casper-client put-transaction session \
--session-arg "odra_cfg_package_hash_key_name:string:'test_contract_package_hash'" \
--session-arg "odra_cfg_allow_key_override:bool:'true'" \
--session-arg "odra_cfg_is_upgradable:bool:'true'" \
--session-arg "odra_cfg_is_upgrade:bool:'false'" \
--session-arg "name:string='My Name'" \
--session-arg "description:string='My Description'" \
--session-arg "price_1:u256='101'" \
Expand Down
12 changes: 9 additions & 3 deletions docusaurus/docs/tutorials/odra-cli.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
sidebar_position: 12
---

# Odra CLI

The Odra CLI library allows you to create CLI clients for your contracts. Instead of using `casper-client` with
Expand Down Expand Up @@ -102,9 +106,11 @@ impl DeployScript for DeployDogScript {

In the example above, we see a few alternative implementations of a simple `DeployScript` for our `DogContract`. All of them set the gas limit,
deploy the contract and adds it to a container.
The first one uses the `DogContract::try_deploy` method, which deploys the contract every time the script is run. The second also deploys a contract everytime,
but passes [`InstallConfig`] instance to configure the deployment using a factory method `InstallConfig::upgradable`.
A next option utilizes the [`DeployerExt`] trait, which checks if the contract is already deployed and returns the existing instance if it is, or deploys it if it is not. It is a convenient way to ensure that the contract is deployed only once. It is useful when you want to add more contracts to the script in the future and avoid redeploying previously deployed contracts. The last option is to use `load_or_deploy_with_cfg` that accepts a custom configuration.

1. `DogContract::try_deploy` method, which deploys the contract every time the script is run.
2. `DogContract::try_deploy_with_cfg` also deploys a contract everytime, but passes [`InstallConfig`] instance to configure the deployment using a factory method `InstallConfig::upgradable`.
3. Utilizes the [`DeployerExt`] trait, which checks if the contract is already deployed and returns the existing instance if it is, or deploys it if it is not. It is a convenient way to ensure that the contract is deployed only once. It is useful when you want to add more contracts to the script in the future and avoid redeploying previously deployed contracts.
4. The last option is to use `DeployerExt::load_or_deploy_with_cfg` that works like the previous one, but accepts a custom configuration.

The address of the deployed contract is stored in a TOML file in the `resources` directory, which is created automatically by the Odra CLI library.

Expand Down
154 changes: 154 additions & 0 deletions docusaurus/docs/tutorials/upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
sidebar_position: 11
---

# Upgrading Contracts

This tutorial demonstrates how to upgrade a contract deployed on the Casper Livenet environment using Odra.
It is a continuation of the previous tutorial on [deploying contracts](./deploying-on-casper.md).
If you didn't follow the previous tutorial, please do so before continuing.


## Livenet Example

In this example, we will deploy a simple counter contract and then upgrade it to a new version with additional features.
We will skip the details of the contract implementation and focus on the deployment and upgrade process.

Our example deploys a simple counter contract making it upgradable and then upgrades it twice: first to `CounterV2`, and then back to `CounterV1`.

```rust title=examples/bin/upgrades_on_livenet.rs
//! This example demonstrates how to deploy and upgrade a contract on the Livenet environment.

use odra::casper_types::U256;
use odra::host::{Deployer, HostRef, InstallConfig, NoArgs};
use odra_examples::features::upgrade::{CounterV1, CounterV2, CounterV2UpgradeArgs};

fn main() {
let env = odra_casper_livenet_env::env();

env.set_gas(500_000_000_000u64);

// Contracts can be upgraded
let mut counter =
CounterV1::deploy_with_cfg(&env, NoArgs, InstallConfig::upgradable::<CounterV1>());

env.set_gas(50_000_000_000u64);
counter.increment();
assert_eq!(counter.get(), 1);

env.set_gas(500_000_000_000u64);
let mut counter2 = CounterV2::try_upgrade(
&env,
counter.contract_address(),
CounterV2UpgradeArgs { new_start: None }
)
.unwrap();

env.set_gas(50_000_000_000u64);
counter2.increment();
assert_eq!(counter2.get(), U256::from(2));

env.set_gas(500_000_000_000u64);
let mut counter3 = CounterV1::try_upgrade(&env, counter.contract_address(), NoArgs).unwrap();

env.set_gas(50_000_000_000u64);
counter3.increment();
assert_eq!(counter3.get(), 2);
}
```

The `Deployer` trait has two functions that allow upgrading contracts:

- `try_upgrade`: upgrades a contract to a new version. It takes the environment, the address of the contract to be upgraded, and the arguments for the new version of the contract. The function
- `try_upgrade_with_cfg`: same as `try_upgrade`, but allows specifying a custom [upgrade configuration].

Let's take a quick look at the `CounterV2` implementation:

```rust
#[odra::module]
impl CounterV2 {
pub fn init(&mut self, start_from: Option<U256>) {
if let Some(start) = start_from {
self.new_counter.set(start);
} else {
self.new_counter.set(U256::from(0));
}
}

pub fn upgrade(&mut self, new_start: Option<U256>) {
if let Some(start) = new_start {
self.new_counter.set(start);
} else {
// If no new value is provided, we keep the current value
self.new_counter.set(self.counter.get_or_default().into());
}
}

...
}
```

The contract implements the `upgrade` function, which allows executing the upgrade logic for the contract. When upgrading to a new version, the `upgrade` function is called with the new initialization parameters. We call the `try_upgrade` function with `CounterV2UpgradeArgs` - a struct [automatically generated] by the Odra framework. It is a mirror feature of the contract's initialization parameters.

## Run the example

Now, let's see the code in action!

```bash
cargo run --bin our_token_livenet --features livenet
```

A sample output of the program might look like this:

```
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
Running `../target/debug/upgrade_on_livenet`
💁 INFO : Found wasm under "/Users/kpob/workspace/odra/examples/wasm/CounterV1.wasm".
💁 INFO : Deploying "CounterV1".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(301469239d731d7b7ca9aef7a42dcb1473a1388d59e1bac619a008e7b555aa0d)).
💁 INFO : Transaction "301469239d731d7b7ca9aef7a42dcb1473a1388d59e1bac619a008e7b555aa0d" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/301469239d731d7b7ca9aef7a42dcb1473a1388d59e1bac619a008e7b555aa0d
💁 INFO : Contract "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" deployed.
💁 INFO : Calling "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" directly with entrypoint "increment".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(a0fb180018063702094cdd66bdda1a3eda6f90c2e726334f202d77cddd88e649)).
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(a0fb180018063702094cdd66bdda1a3eda6f90c2e726334f202d77cddd88e649)).
💁 INFO : Transaction "a0fb180018063702094cdd66bdda1a3eda6f90c2e726334f202d77cddd88e649" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/a0fb180018063702094cdd66bdda1a3eda6f90c2e726334f202d77cddd88e649
💁 INFO : Found wasm under "/Users/kpob/workspace/odra/examples/wasm/CounterV2.wasm".
💁 INFO : Deploying "CounterV2".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(ec4518f51a2b3ed19886d6cd2d67e138aeb24834362d8fad8adb4af45fa36f21)).
💁 INFO : Transaction "ec4518f51a2b3ed19886d6cd2d67e138aeb24834362d8fad8adb4af45fa36f21" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/ec4518f51a2b3ed19886d6cd2d67e138aeb24834362d8fad8adb4af45fa36f21
💁 INFO : Contract "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" deployed.
💁 INFO : Calling "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" directly with entrypoint "increment".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(cc694727bf166c55e2080578fcf6dc944e2d9b931b8301fe5208c8e29e8c599a)).
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(cc694727bf166c55e2080578fcf6dc944e2d9b931b8301fe5208c8e29e8c599a)).
💁 INFO : Transaction "cc694727bf166c55e2080578fcf6dc944e2d9b931b8301fe5208c8e29e8c599a" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/cc694727bf166c55e2080578fcf6dc944e2d9b931b8301fe5208c8e29e8c599a
💁 INFO : Found wasm under "/Users/kpob/workspace/odra/examples/wasm/CounterV1.wasm".
💁 INFO : Deploying "CounterV1".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(58453efdf683f2271f558a22e406631edccff2771867069c2cbefb3386fc8833)).
💁 INFO : Transaction "58453efdf683f2271f558a22e406631edccff2771867069c2cbefb3386fc8833" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/58453efdf683f2271f558a22e406631edccff2771867069c2cbefb3386fc8833
💁 INFO : Contract "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" deployed.
💁 INFO : Calling "contract-package-b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12" directly with entrypoint "increment".
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(5cd3920e6b7cab505e074eb5684cc1b191841d2f24a889079d9fb1a92167fd4f)).
🙄 WAIT : Waiting 10 for V1(TransactionV1Hash(5cd3920e6b7cab505e074eb5684cc1b191841d2f24a889079d9fb1a92167fd4f)).
💁 INFO : Transaction "5cd3920e6b7cab505e074eb5684cc1b191841d2f24a889079d9fb1a92167fd4f" successfully executed.
🔗 LINK : https://testnet.cspr.live/transaction/5cd3920e6b7cab505e074eb5684cc1b191841d2f24a889079d9fb1a92167fd4f
```

## Cspr.live

Let's take a look at cspr.live: https://testnet.cspr.live/contract-package/b8b5003fe3ba05b4cae2e8acaeb777520e025958e12bd15394bf42e96f3f7b12

It works! Now our contracts has three versions deployed:

![versions.png](../versions.png)

## Conclusion

In this tutorial, we learned how to upgrade a smart contract on the blockchain using the Odra framework. We deployed a simple counter contract, made it upgradable, and then upgraded it twice: first to `CounterV2`, and then back to `CounterV1`. We also explored the testnet to verify our contract deployments.

[upgrade configuration]: https://docs.rs/odra/2.3.0/odra/host/struct.UpgradeConfig.html
[automatically generated]: https://docs.rs/odra/2.3.0/odra/host/trait.UpgradeArgs.html
1 change: 1 addition & 0 deletions docusaurus/docs/tutorials/using-proxy-caller.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export async function deploy_contract(): Promise<string> {
odra_cfg_package_hash_key_name: CLValueBuilder.string("tlw"),
odra_cfg_allow_key_override: CLValueBuilder.bool(true),
odra_cfg_is_upgradable: CLValueBuilder.bool(true),
odra_cfg_is_upgrade: CLValueBuilder.bool(false),
lock_duration: CLValueBuilder.u64(60 * 60)
});

Expand Down
Binary file added docusaurus/docs/versions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docusaurus/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const config = {
sidebarPath: require.resolve('./sidebars.js'),
includeCurrentVersion: true,
showLastUpdateTime: true,
lastVersion: '2.2.0',
lastVersion: '2.3.0',
versions: {
current: {
label: 'next',
Expand Down
112 changes: 112 additions & 0 deletions docusaurus/versioned_docs/version-2.3.0/advanced/01-delegate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Delegate

Managing boilerplate code can often lead to code that is cumbersome and challenging to comprehend. The Odra library introduces a solution to this issue with its Delegate feature. As the name implies, the Delegate feature permits the delegation of function calls to child modules, effectively minimizing the redundancy of boilerplate code and maintaining a lean and orderly parent module.

The main advantage of this feature is that it allows you to inherit the default behavior of child modules seamlessly, making your contracts more readable.

## Overview

To utilize the delegate feature in your contract, use the `delegate!` macro provided by Odra. This macro allows you to list the functions you wish to delegate to the child modules. By using the `delegate!` macro, your parent module remains clean and easy to understand.

You can delegate functions to as many child modules as you like. The functions will be available as if they were implemented in the parent module itself.

## Code Examples

Consider the following basic example for better understanding:

```rust
use crate::{erc20::Erc20, ownable::Ownable};
use odra::{casper_types::U256, prelude::*};

#[odra::module]
pub struct OwnedToken {
ownable: SubModule<Ownable>,
erc20: SubModule<Erc20>
}

#[odra::module]
impl OwnedToken {
pub fn init(&mut self, name: String, symbol: String, decimals: u8, initial_supply: U256) {
let deployer = self.env().caller();
self.ownable.init(deployer);
self.erc20.init(name, symbol, decimals, initial_supply);
}

delegate! {
to self.erc20 {
fn transfer(&mut self, recipient: Address, amount: U256);
fn transfer_from(&mut self, owner: Address, recipient: Address, amount: U256);
fn approve(&mut self, spender: Address, amount: U256);
fn name(&self) -> String;
fn symbol(&self) -> String;
fn decimals(&self) -> u8;
fn total_supply(&self) -> U256;
fn balance_of(&self, owner: Address) -> U256;
fn allowance(&self, owner: Address, spender: Address) -> U256;
}

to self.ownable {
fn get_owner(&self) -> Address;
fn change_ownership(&mut self, new_owner: Address);
}
}

pub fn mint(&mut self, address: Address, amount: U256) {
self.ownable.ensure_ownership(self.env().caller());
self.erc20.mint(address, amount);
}
}
```

This `OwnedToken` contract includes two modules: `Erc20` and `Ownable`. We delegate various functions from both modules using the `delegate!` macro. As a result, the contract retains its succinctness without compromising on functionality.

The above example basically merges the functionalities of modules and adds some control over the minting process. But you can use delegation to build more complex contracts, cherry-picking just a few module functionalities.

Let's take a look at another example.

```rust
use crate::{erc20::Erc20, ownable::Ownable, exchange::Exchange};
use odra::{casper_types::U256, prelude::*};

#[odra::module]
pub struct DeFiPlatform {
ownable: SubModule<Ownable>,
erc20: SubModule<Erc20>,
exchange: SubModule<Exchange>
}

#[odra::module]
impl DeFiPlatform {
pub fn init(&mut self, name: String, symbol: String, decimals: u8, initial_supply: U256, exchange_rate: u64) {
let deployer = self.env().caller();
self.ownable.init(deployer);
self.erc20.init(name, symbol, decimals, initial_supply);
self.exchange.init(exchange_rate);
}

delegate! {
to self.erc20 {
fn transfer(&mut self, recipient: Address, amount: U256);
fn balance_of(&self, owner: Address) -> U256;
}

to self.ownable {
fn get_owner(&self) -> Address;
}

to self.exchange {
fn swap(&mut self, sender: Address, recipient: Address);
fn set_exchange_rate(&mut self, new_rate: u64);
}
}

pub fn mint(&mut self, address: Address, amount: U256) {
self.ownable.ensure_ownership(self.env().caller());
self.erc20.mint(address, amount);
}
}
```

In this `DeFiPlatform` contract, we include `Erc20`, `Ownable`, and `Exchange` modules. By delegating functions from these modules, the parent contract becomes a powerhouse of functionality while retaining its readability and structure.

Remember, the possibilities are endless with Odra's. By leveraging this feature, you can write cleaner, more efficient, and modular smart contracts.
Loading