Skip to content

Commit

Permalink
feat: reintroduce upgradable contract examples (#41)
Browse files Browse the repository at this point in the history
Description
===========
This PR serves two purposes:
    - Adjusts ink-examples to align with integration
      test repositories.
    - Fixes the previous URL check failure from Ink-docs.
      For more context, please refer to Ink-docs PR 273.

Impact
======
This PR addresses markdown-link-check issue in Ink-docs:
use-ink/ink-docs#273

* fix: update set-code-hash path in ci

* fix: adjust ci for upgradable-contracts directory

Description
===========
- Added environment variable UPGRADEABLE_CONTRACTS.
- Adjusted ci to process contracts under the
  upgradeable-contracts directory.
- Removed unnecessary foo, bar feature flags from the
  CI steps as they were not present in Cargo.toml files
  • Loading branch information
0xf333 authored Sep 12, 2023
1 parent 616ac4c commit b0e00fb
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 40 deletions.
30 changes: 17 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ jobs:
- test
runs-on: ${{ matrix.platform }}
env:
MULTI_CONTRACT_CALLER_SUBCONTRACTS: "accumulator adder subber"
RUST_BACKTRACE: full
MULTI_CONTRACT_CALLER_SUBCONTRACTS: "accumulator adder subber"
UPGRADEABLE_CONTRACTS: "delegator set-code-hash"
RUST_BACKTRACE: full
steps:

- name: Checkout sources & submodules
Expand Down Expand Up @@ -103,11 +104,13 @@ jobs:
echo "Processing multi-contract-caller contract: $contract";
cargo ${{ matrix.job }} --verbose --manifest-path multi-contract-caller/${contract}/Cargo.toml;
}
cargo ${{ matrix.job }} --verbose --manifest-path set-code-hash/updated-incrementer/Cargo.toml;
cargo ${{ matrix.job }} --verbose --manifest-path conditional-compilation/Cargo.toml --features foo;
cargo ${{ matrix.job }} --verbose --manifest-path conditional-compilation/Cargo.toml --features bar;
cargo ${{ matrix.job }} --verbose --manifest-path conditional-compilation/Cargo.toml --features "foo, bar";
$upgradeable_contracts = "delegator","set-code-hash"
foreach ($contract in $upgradeable_contracts) {
echo "Processing upgradeable contract: $contract";
cargo ${{ matrix.job }} --verbose --manifest-path upgradeable-contracts/${contract}/Cargo.toml;
}
foreach ($example in Get-ChildItem -Directory "\*") {
if ($example -Match 'artifacts') { continue }
echo "Processing example: $example";
Expand All @@ -122,13 +125,14 @@ jobs:
echo "Processing multi-contract-caller contract: $contract";
cargo ${{ matrix.job }} --verbose --manifest-path multi-contract-caller/${contract}/Cargo.toml;
done
cargo ${{ matrix.job }} --verbose --manifest-path=set-code-hash/updated-incrementer/Cargo.toml;
cargo ${{ matrix.job }} --verbose --manifest-path=conditional-compilation/Cargo.toml --features=foo;
cargo ${{ matrix.job }} --verbose --manifest-path=conditional-compilation/Cargo.toml --features=bar;
cargo ${{ matrix.job }} --verbose --manifest-path=conditional-compilation/Cargo.toml --features="foo, bar";
for contract in ${UPGRADEABLE_CONTRACTS}; do
echo "Processing upgradeable contract: $contract";
cargo ${{ matrix.job }} --verbose --manifest-path=upgradeable-contracts/$contract/Cargo.toml;
done
for example in ./*/; do
if [ "$example" = "./artifacts/" ]; then continue; fi;
echo "Processing example: $example";
cargo ${{ matrix.job }} --verbose --manifest-path=$example/Cargo.toml;
if [ "$example" = "./artifacts/" ] || [ "$example" = "./upgradeable-contracts/" ]; then continue; fi;
echo "Processing example: $example";
cargo ${{ matrix.job }} --verbose --manifest-path=$example/Cargo.toml;
done
36 changes: 36 additions & 0 deletions upgradeable-contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Upgradeable Contracts

There are different ways a contract can be upgraded in ink!

This folder illustrates some of the common and best practices to achieve upgradeability in your contracts.

## [`set-code-hash`](set-code-hash/)

ink! provides an ability to replace the code under the given contract's address.
This is exactly what `set_code_hash()` function does.

However, developers needs to be mindful of storage compatibility.
You can read more about storage compatibility on [use.ink](https://use.ink/basics/upgradeable-contracts#replacing-contract-code-with-set_code_hash)

## [Delegator](delegator/)

Delegator patter is based around a low level cross contract call function `delegate_call`.
It allows a contract to delegate its execution to some on-chain uploaded code.

It is different from a traditional cross-contract call
because the call is delegate to the **code**, not the contract.

Similarly, the storage compatibility issue is also applicable here.
However, there are certain nuances associated with using `delegate_call`.

First of all, as demonstrated in the example, if the delegated code intends to mutate the caller's storage,
a developer needs to be mindful. If the delegated code modifies layout-full storage
(i.e. it contains at least non-`Lazy`, non-`Mapping` field), the `.set_tail_call(true)` flag of `CallFlags` needs to be specified and the storage layouts must match.
This is due to the way ink! execution call stack is operated
(see [Stack Exchange Answer](https://substrate.stackexchange.com/a/3352/3098) for more explanation).

If the delegated code only modifies `Lazy` or `Mapping` field, the keys must be identical and `.set_tail_call(true)` is optional.
This is because `Lazy` and `Mapping` interact with the storage directly instead of loading and flushing storage states.

If your storage is completely layoutless (it only contains `Lazy` and `Mapping` fields), the order of fields and layout do not need to match for the same reason as mentioned above.

9 changes: 9 additions & 0 deletions upgradeable-contracts/delegator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
29 changes: 29 additions & 0 deletions upgradeable-contracts/delegator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "delegator"
version = "4.3.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
publish = false

[dependencies]
ink = { version = "4.3", default-features = false }
delegatee = { path = "delegatee", default-features = false, features = ["ink-as-dependency"] }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.9", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = { version = "4.3" }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []
e2e-tests = []
9 changes: 9 additions & 0 deletions upgradeable-contracts/delegator/delegatee/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "updated_incrementer"
name = "delegatee"
version = "4.3.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
Expand All @@ -9,7 +9,7 @@ publish = false
ink = { version = "4.3", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.5", default-features = false, features = ["derive"], optional = true }
scale-info = { version = "2.9", default-features = false, features = ["derive"], optional = true }

[lib]
path = "lib.rs"
Expand All @@ -22,3 +22,6 @@ std = [
"scale-info/std",
]
ink-as-dependency = []
e2e-tests = []


43 changes: 43 additions & 0 deletions upgradeable-contracts/delegator/delegatee/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

#[ink::contract]
pub mod delegatee {
use ink::storage::{
traits::ManualKey,
Mapping,
};
#[ink(storage)]
pub struct Delegatee {
addresses: Mapping<AccountId, i32, ManualKey<0x23>>,
counter: i32,
// Uncommenting below line will break storage compatibility.
// flag: bool,
}

impl Delegatee {
/// When using the delegate call. You only upload the code of the delegatee
/// contract. However, the code and storage do not get initialized.
///
/// Because of this. The constructor actually never gets called.
#[allow(clippy::new_without_default)]
#[ink(constructor)]
pub fn new() -> Self {
unreachable!(
"Constructors are not called when upgrading using `set_code_hash`."
)
}

/// Increments the current value.
#[ink(message)]
pub fn inc(&mut self) {
self.counter += 2;
}

/// Adds current value of counter to the `addresses`
#[ink(message)]
pub fn append_address_value(&mut self) {
let caller = self.env().caller();
self.addresses.insert(caller, &self.counter);
}
}
}
Loading

0 comments on commit b0e00fb

Please sign in to comment.