Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Kreivo APIs for Contracts #445

Merged
merged 7 commits into from
Jan 27, 2025
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
1,220 changes: 882 additions & 338 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["chain-spec-generator", "runtime/kreivo", "common"]
members = ["apis", "chain-spec-generator", "common", "runtime/kreivo"]
resolver = "2"

[profile.release]
Expand Down Expand Up @@ -27,10 +27,10 @@ futures = { version = "0.3.28" }
hex-literal = { version = "0.4.1" }
log = { version = "0.4.22" }
parity-scale-codec = { version = "3.6.4", default-features = false, features = [
"derive",
"derive",
] }
scale-info = { version = "2.10.0", default-features = false, features = [
"derive",
"derive",
] }
serde = { version = "1.0.188", default-features = false }
serde_json = { version = "1.0.121", default-features = false }
Expand All @@ -40,6 +40,7 @@ smallvec = "1.11"
kreivo-runtime = { path = "runtime/kreivo" }

# Virto Pallets
kreivo-apis = { default-features = false, path = "apis" }
virto-common = { default-features = false, path = "common" }

pallet-communities = { default-features = false, path = "pallets/communities" }
Expand Down Expand Up @@ -158,3 +159,6 @@ polkadot-runtime-common = { default-features = false, git = "https://github.com/
xcm = { package = "staging-xcm", default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409" }
xcm-builder = { package = "staging-xcm-builder", default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409" }
xcm-executor = { package = "staging-xcm-executor", default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409" }

# ink!
ink = { version = "5.1.1", default-features = false }
45 changes: 45 additions & 0 deletions apis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "kreivo-apis"
description = "A set of APIs to connect Smart Contract creators with the Kreivo runtime."
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
frame-support = { workspace = true }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }

# Runtime
frame-system = { workspace = true, optional = true }
pallet-contracts = { workspace = true, optional = true }
log = { workspace = true, optional = true }

# Contract
ink = { workspace = true, optional = true }
virto-common = { workspace = true, optional = true, features = ["scale"] }

[features]
default = ["std", "runtime", "contract"]
std = [
"frame-support/std",
"frame-system/std",
"ink?/std",
"log?/std",
"pallet-contracts?/std",
"parity-scale-codec/std",
"scale-info/std",
"virto-common?/std",
]
runtime = [
"dep:frame-system",
"dep:log",
"dep:pallet-contracts",
]
contract = [
"dep:ink",
"dep:virto-common"
]
14 changes: 14 additions & 0 deletions apis/src/apis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mod assets;
mod error;

pub use assets::*;
pub use error::*;

/// A set of APIs to interact between applications (like Smart Contracts) and
/// the Kreivo runtime.
pub trait KreivoAPI<Env> {
/// Manipulation of arbitrary assets.
type Assets: AssetsAPI<Env>;

fn assets(&self) -> &Self::Assets;
}
40 changes: 40 additions & 0 deletions apis/src/apis/assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! # Assets APIs
//!
//! Facilitate transactions using arbitrary assets between external callers and
//! the application.
//!
//! ## Methods
//!
//! The supported methods are:
//!
//! - **[`deposit`][AssetsAPI::deposit]:** Receives an `amount` of a certain
//! `asset` from the caller of the application. Then, deposits that amount
//! into the balance of the application asset account.
//! - **[`transfer`][AssetsAPI::transfer]:** Transfers an `amount` of a certain
//! `asset` to a `beneficiary`.

use crate::apis::error::KreivoApisError;
use frame_support::Parameter;

/// An API for transacting with arbitrary assets.
pub trait AssetsAPI<Env> {
type AccountId: Parameter;
type AssetId: Parameter;
type Balance: Parameter + Copy;

/// Returns the balance of an asset account.
fn balance(&self, asset: Self::AssetId, who: &Self::AccountId) -> Self::Balance;

/// Receives an `amount` of a certain `asset` from the caller of the
/// application. Then, deposits that amount into the balance of the
/// application asset account.
fn deposit(&self, asset: Self::AssetId, amount: Self::Balance) -> Result<Self::Balance, KreivoApisError>;

/// Transfers an `amount` of a certain `asset` to a `beneficiary`.
fn transfer(
&self,
asset: Self::AssetId,
amount: Self::Balance,
beneficiary: &Self::AccountId,
) -> Result<Self::Balance, KreivoApisError>;
}
102 changes: 102 additions & 0 deletions apis/src/apis/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use parity_scale_codec::{Decode, Encode, Error};
use scale_info::TypeInfo;

#[derive(Encode, Decode, Debug, Clone, Copy)]
pub struct KreivoApisErrorCode(u32);

impl From<KreivoApisErrorCode> for u32 {
fn from(value: KreivoApisErrorCode) -> Self {
value.0
}
}

impl From<u32> for KreivoApisErrorCode {
fn from(value: u32) -> Self {
Self(value)
}
}

impl From<Error> for KreivoApisErrorCode {
fn from(_: Error) -> Self {
panic!("encountered unexpected invalid SCALE encoding")
}
}

#[derive(TypeInfo, Encode, Decode, Clone, Debug, PartialEq)]
pub enum KreivoApisError {
UnknownError,
ExtQueryError,
Assets(AssetsApiError),
}

impl From<KreivoApisError> for KreivoApisErrorCode {
fn from(error: KreivoApisError) -> KreivoApisErrorCode {
match error {
KreivoApisError::UnknownError => Self(1),
KreivoApisError::ExtQueryError => Self(2),
KreivoApisError::Assets(e) => Self(1u32 << 16 | e as u16 as u32),

Check warning on line 37 in apis/src/apis/error.rs

View workflow job for this annotation

GitHub Actions / clippy

operator precedence can trip the unwary

warning: operator precedence can trip the unwary --> apis/src/apis/error.rs:37:39 | 37 | KreivoApisError::Assets(e) => Self(1u32 << 16 | e as u16 as u32), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(1u32 << 16) | e as u16 as u32` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#precedence = note: `#[warn(clippy::precedence)]` on by default
}
}
}

impl From<AssetsApiError> for KreivoApisError {
fn from(error: AssetsApiError) -> Self {
KreivoApisError::Assets(error)
}
}

impl From<KreivoApisErrorCode> for KreivoApisError {
fn from(value: KreivoApisErrorCode) -> Self {
match value.0 {
0x00000002 => KreivoApisError::ExtQueryError,
0x00010000..0x0001ffff => {
TryFrom::<KreivoApisErrorCode>::try_from(KreivoApisErrorCode(value.0 & 0x0000ffff))
.map(KreivoApisError::Assets)
.unwrap_or(KreivoApisError::UnknownError)
}
_ => KreivoApisError::UnknownError,
}
}
}

#[repr(u16)]
#[derive(TypeInfo, Encode, Decode, Clone, Debug, PartialEq)]
pub enum AssetsApiError {
CannotDeposit,
CannotTransfer,
}

impl TryFrom<KreivoApisErrorCode> for AssetsApiError {
type Error = ();

fn try_from(value: KreivoApisErrorCode) -> Result<Self, Self::Error> {
match value.0 {
0 => Ok(AssetsApiError::CannotDeposit),
1 => Ok(AssetsApiError::CannotTransfer),
_ => Err(()),
}
}
}

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

macro_rules! test_error_code_conversion {
($error: expr) => {{
let error: KreivoApisError = $error.into();
let error_code: KreivoApisErrorCode = error.clone().into();
let error_back: KreivoApisError = error_code.clone().into();
assert_eq!(error, error_back);
}};
}

#[test]
fn convert_from_error_to_error_code_back_to_error_works() {
test_error_code_conversion!(KreivoApisError::UnknownError);
test_error_code_conversion!(KreivoApisError::ExtQueryError);

test_error_code_conversion!(AssetsApiError::CannotDeposit);
test_error_code_conversion!(AssetsApiError::CannotTransfer);
}
}
39 changes: 39 additions & 0 deletions apis/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::*;

use contract::config::{AssetsConfig, Config};
use ink::env::{DefaultEnvironment, Environment};

mod chain_extension;
pub mod config;

#[derive(Clone)]
pub struct KreivoApiEnvironment<E: Clone = DefaultEnvironment>(E);

impl<E: Clone> KreivoApiEnvironment<E> {
pub fn new(env: E) -> Self {
Self(env)
}
}

impl<E> Environment for KreivoApiEnvironment<E>
where
E: Environment,
{
const MAX_EVENT_TOPICS: usize = E::MAX_EVENT_TOPICS;
type AccountId = E::AccountId;
type Balance = E::Balance;
type Hash = E::Hash;
type Timestamp = E::Timestamp;
type BlockNumber = E::BlockNumber;
type ChainExtension = chain_extension::ChainExtension;
}

impl<E: Environment> Config for KreivoApiEnvironment<E> {
type AccountId = E::AccountId;
type Balance = E::Balance;
type Assets = Self;
}

impl<E: Environment> AssetsConfig for KreivoApiEnvironment<E> {
type AssetId = virto_common::FungibleAssetLocation;
}
42 changes: 42 additions & 0 deletions apis/src/contract/chain_extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use super::*;

use crate::apis::KreivoApisErrorCode;
use config::{AccountIdOf, AssetIdOf, BalanceOf};
use ink::chain_extension;

type Environment = KreivoApiEnvironment;

#[chain_extension(extension = 0)]
pub trait ChainExtension {
type ErrorCode = KreivoApisErrorCode;

// Assets
#[allow(non_snake_case)]
#[ink(function = 0x0000, handle_status = false)]
fn assets__balance(asset: AssetIdOf<Environment>, who: AccountIdOf<Environment>) -> BalanceOf<Environment>;

#[allow(non_snake_case)]
#[ink(function = 0x0001)]
fn assets__deposit(
asset: AssetIdOf<Environment>,
amount: BalanceOf<Environment>,
) -> Result<BalanceOf<Environment>, KreivoApisErrorCode>;

#[allow(non_snake_case)]
#[ink(function = 0x0002)]
fn assets__transfer(
asset: AssetIdOf<Environment>,
amount: BalanceOf<Environment>,
beneficiary: BalanceOf<Environment>,
) -> Result<BalanceOf<Environment>, KreivoApisErrorCode>;
}

impl ink::env::chain_extension::FromStatusCode for KreivoApisErrorCode {
fn from_status_code(code: u32) -> Result<(), Self> {
if code == 0 {
Ok(())
} else {
Err(code.into())
}
}
}
13 changes: 13 additions & 0 deletions apis/src/contract/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub trait Config {
type AccountId;
type Balance;
type Assets;
}

pub trait AssetsConfig {
type AssetId;
}

pub type AccountIdOf<T> = <T as Config>::AccountId;
pub type BalanceOf<T> = <T as Config>::Balance;
pub type AssetIdOf<T> = <<T as Config>::Assets as AssetsConfig>::AssetId;
24 changes: 24 additions & 0 deletions apis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![cfg_attr(not(any(test, feature = "std")), no_std)]

//! # Kreivo APIs
//!
//! This is a set of APIs that can be used in an application context (like Smart
//! Contracts) to interact with Kreivo.
//!
//! ## APIs
//!
//! Currently available APIs include:
//!
//! - **[`AssetsAPI`][apis::AssetsAPI]:** These APIs can facilitate transactions
//! regarding assets.

pub mod apis;
#[cfg(feature = "contract")]
mod contract;
#[cfg(feature = "runtime")]
mod runtime;

#[cfg(feature = "contract")]
pub use contract::KreivoApiEnvironment;
#[cfg(feature = "runtime")]
pub use runtime::KreivoChainExtensions;
Loading
Loading