Skip to content

Commit

Permalink
feat(pop-api): implement pop api crate, chain extension, and demo con…
Browse files Browse the repository at this point in the history
…tracts (#20)
  • Loading branch information
peterwht authored Mar 6, 2024
1 parent 88738a5 commit 27da803
Show file tree
Hide file tree
Showing 28 changed files with 3,178 additions and 921 deletions.
2,449 changes: 1,642 additions & 807 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ repository = "https://github.com/r0gue-io/pop-node/"

[workspace]
members = [
"node",
"runtime",
"node",
"runtime",
"pop-api",
]
exclude = [
"contracts"
]

resolver = "2"

[workspace.dependencies]
Expand All @@ -32,7 +37,9 @@ substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", b
substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.1" }

# Local

pop-runtime = { path = "./runtime" }
pop-api-primitives = { path = "./pop-api/primitives", default-features = false }

# Substrate
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.7.1" }
Expand Down
9 changes: 9 additions & 0 deletions contracts/pop-api-examples/balance-transfer/.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
28 changes: 28 additions & 0 deletions contracts/pop-api-examples/balance-transfer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "pop_api_extension_demo"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2021"

[dependencies]
ink = { version = "4.3.0", default-features = false }
pop-api = { path = "../../../pop-api", default-features = false }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = "4.3.0"

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"pop-api/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []
e2e-tests = []
134 changes: 134 additions & 0 deletions contracts/pop-api-examples/balance-transfer/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

use pop_api::balances;

#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum ContractError {
BalancesError(balances::Error),
}

impl From<balances::Error> for ContractError {
fn from(value: balances::Error) -> Self {
ContractError::BalancesError(value)
}
}

#[ink::contract(env = pop_api::Environment)]
mod pop_api_extension_demo {
use super::ContractError;

#[ink(storage)]
#[derive(Default)]
pub struct PopApiExtensionDemo;

impl PopApiExtensionDemo {
#[ink(constructor, payable)]
pub fn new() -> Self {
ink::env::debug_println!("PopApiExtensionDemo::new");
Default::default()
}

#[ink(message)]
pub fn transfer_through_runtime(
&mut self,
receiver: AccountId,
value: Balance,
) -> Result<(), ContractError> {
ink::env::debug_println!(
"PopApiExtensionDemo::transfer_through_runtime: \nreceiver: {:?}, \nvalue: {:?}",
receiver,
value
);

pop_api::balances::transfer_keep_alive(receiver, value)?;

ink::env::debug_println!("PopApiExtensionDemo::transfer_through_runtime end");
Ok(())
}
}

#[cfg(all(test, feature = "e2e-tests"))]
mod e2e_tests {
use super::*;
use ink_e2e::{ChainBackend, ContractsBackend};

use ink::{
env::{test::default_accounts, DefaultEnvironment},
primitives::AccountId,
};

type E2EResult<T> = Result<T, Box<dyn std::error::Error>>;

/// The base number of indivisible units for balances on the
/// `substrate-contracts-node`.
const UNIT: Balance = 1_000_000_000_000;

/// The contract will be given 1000 tokens during instantiation.
const CONTRACT_BALANCE: Balance = 1_000 * UNIT;

/// The receiver will get enough funds to have the required existential deposit.
///
/// If your chain has this threshold higher, increase the transfer value.
const TRANSFER_VALUE: Balance = 1 / 10 * UNIT;

/// An amount that is below the existential deposit, so that a transfer to an
/// empty account fails.
///
/// Must not be zero, because such an operation would be a successful no-op.
const INSUFFICIENT_TRANSFER_VALUE: Balance = 1;

/// Positive case scenario:
/// - the call is valid
/// - the call execution succeeds
#[ink_e2e::test]
async fn transfer_with_call_runtime_works<Client: E2EBackend>(
mut client: Client,
) -> E2EResult<()> {
// given
let mut constructor = RuntimeCallerRef::new();
let contract = client
.instantiate("call-runtime", &ink_e2e::alice(), &mut constructor)
.value(CONTRACT_BALANCE)
.submit()
.await
.expect("instantiate failed");
let mut call_builder = contract.call_builder::<RuntimeCaller>();

let accounts = default_accounts::<DefaultEnvironment>();

let receiver: AccountId = accounts.bob;

let sender_balance_before = client
.free_balance(accounts.alice)
.await
.expect("Failed to get account balance");
let receiver_balance_before =
client.free_balance(receiver).await.expect("Failed to get account balance");

// when
let transfer_message = call_builder.transfer_through_runtime(receiver, TRANSFER_VALUE);

let call_res = client
.call(&ink_e2e::alice(), &transfer_message)
.submit()
.await
.expect("call failed");

assert!(call_res.return_value().is_ok());

// then
let sender_balance_after = client
.free_balance(accounts.alice)
.await
.expect("Failed to get account balance");
let receiver_balance_after =
client.free_balance(receiver).await.expect("Failed to get account balance");

assert_eq!(contract_balance_before, contract_balance_after + TRANSFER_VALUE);
assert_eq!(receiver_balance_before, receiver_balance_after - TRANSFER_VALUE);

Ok(())
}
}
}
9 changes: 9 additions & 0 deletions contracts/pop-api-examples/nfts/.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
28 changes: 28 additions & 0 deletions contracts/pop-api-examples/nfts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "pop_api_nft_example"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2021"

[dependencies]
ink = { version = "4.3.0", default-features = false }
pop-api = { path = "../../../pop-api", default-features = false }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = "4.3.0"

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"pop-api/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []
e2e-tests = []
66 changes: 66 additions & 0 deletions contracts/pop-api-examples/nfts/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

use pop_api::nfts;

#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum ContractError {
NftsError(nfts::Error),
}

impl From<nfts::Error> for ContractError {
fn from(value: nfts::Error) -> Self {
ContractError::NftsError(value)
}
}

mod pop_api_extension_demo {
use super::ContractError;

#[ink(storage)]
#[derive(Default)]
pub struct PopApiExtensionDemo;

impl PopApiExtensionDemo {
#[ink(constructor, payable)]
pub fn new() -> Self {
ink::env::debug_println!("PopApiExtensionDemo::new");
Default::default()
}

#[ink(message)]
pub fn mint_through_runtime(
&mut self,
collection_id: u32,
item_id: u32,
receiver: AccountId,
) -> Result<(), ContractError> {
ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime: collection_id: {:?} \nitem_id {:?} \nreceiver: {:?}, ", collection_id, item_id, receiver);

// simplified API call
let result = pop_api::nfts::mint(collection_id, item_id, receiver);
ink::env::debug_println!(
"PopApiExtensionDemo::mint_through_runtime result: {result:?}"
);
if let Err(pop_api::nfts::Error::NoConfig) = result {
ink::env::debug_println!(
"PopApiExtensionDemo::mint_through_runtime expected error received"
);
}
result?;

ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime end");
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[ink::test]
fn default_works() {
PopApiExtensionDemo::new();
}
}
}
9 changes: 9 additions & 0 deletions contracts/pop-api-examples/read-runtime-state/.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
28 changes: 28 additions & 0 deletions contracts/pop-api-examples/read-runtime-state/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "pop_api_extension_demo"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2021"

[dependencies]
ink = { version = "4.3.0", default-features = false }
pop-api = { path = "../../../pop-api", default-features = false }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
ink_e2e = "4.3.0"

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"pop-api/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []
e2e-tests = []
35 changes: 35 additions & 0 deletions contracts/pop-api-examples/read-runtime-state/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

#[ink::contract(env = pop_api::Environment)]
mod pop_api_extension_demo {
use pop_api::primitives::storage_keys::{
ParachainSystemKeys::LastRelayChainBlockNumber, RuntimeStateKeys::ParachainSystem,
};

#[ink(event)]
pub struct RelayBlockNumberRead {
value: BlockNumber,
}

#[ink(storage)]
#[derive(Default)]
pub struct PopApiExtensionDemo;

impl PopApiExtensionDemo {
#[ink(constructor, payable)]
pub fn new() -> Self {
ink::env::debug_println!("PopApiExtensionDemo::new");
Default::default()
}

#[ink(message)]
pub fn read_relay_block_number(&self) {
let result =
pop_api::state::read::<BlockNumber>(ParachainSystem(LastRelayChainBlockNumber));
ink::env::debug_println!("{:?}", result);
self.env().emit_event(RelayBlockNumberRead {
value: result.expect("Failed to read relay block number."),
});
}
}
}
5 changes: 4 additions & 1 deletion networks/rococo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ id = 909
default_command = "./target/release/pop-node"

[[parachains.collators]]
name = "pop"
name = "pop"
command = "./target/release/pop-node"
port = 9944
args = ["-lruntime::contracts=debug", "-lpopapi::extension=debug"]
Loading

0 comments on commit 27da803

Please sign in to comment.