Skip to content

Commit 49044c2

Browse files
authored
Iris Asset Exchange part 1 (#1)
* add new contract * update deps, update readme * WIP call transfer from contract * pass args to transfer func * add test contract, readme * add payable * update readme * remove test, transfer assets works, add registry, add mint func * add purchase func * update readme * update email * purchase tokens with latest chain ext funcs * update ext * update chain ext, add new errors, add event struct, lock and transfer works * cleanup, add docs
1 parent d70e2d7 commit 49044c2

File tree

5 files changed

+317
-0
lines changed

5 files changed

+317
-0
lines changed

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Contracts
2+
3+
A collection of smart contracts used on the [iris blockchain](https://github.com/iridium-labs/substrate/tree/iris).
4+
5+
## Setup
6+
7+
Follow the [ink! documentation](https://paritytech.github.io/ink-docs/getting-started/setup) for a complete guide on getting started.
8+
9+
To compile a wasm blob and metadata for a contract, navigate to the contract's root directory and run:
10+
11+
``` bash
12+
cargo +nightly contract build
13+
```
14+
15+
### Note on Binaryen/wasm-opt
16+
17+
If your package manager doesn't have binaryen versions >= 99, then:
18+
19+
- Download the latest version here: https://github.com/WebAssembly/binaryen/releases
20+
21+
- follow these instrutions to install:
22+
23+
``` bash
24+
# unzip the tarball
25+
sudo tar xzvf binaryen-version_100-x86_64-linux.tar.gz
26+
# update permissions
27+
chmod +x binaryen-version_100
28+
# move to /opt
29+
sudo mv binaryen-version_100 /opt/
30+
# navigate to /opt
31+
cd /opt
32+
# make it executable
33+
chmod +x binaryen-version_100
34+
# add symbolic link to /usr/bin
35+
sudo ln -s /opt/binaryen-version_100/bin/wasm-opt /usr/bin/wasm-opt
36+
```
37+
38+
Verify the installation by running `wasm-opt --version`. If the command executes and the printed version matches the downloaded version, then the installation is complete.
39+
40+
## Deployment
41+
42+
The simplest method to deploy contracts is to use the polkadot.js ui. After starting an Iris node, navigate to the contracts tab and follow the instructions [here](https://docs.substrate.io/tutorials/v3/ink-workshop/pt1/#creating-an-ink-project).
43+
44+
## Contracts
45+
46+
### Iris Asset Exchange
47+
48+
A decentralized marketplace for exchanging tokens for assets. That is, a marketplace for buying and selling access to and ownership of data.
49+
50+
### Composable Access Rules
51+
52+
Composable Access Rules is a set of contracts that data owners can use to configure additional business logic that must be executed before consumers can access data. These contracts execute when a consumer (token holder) requests data from the network. Rules include contracts such as a "single use" for an owned asset, or placing expiration dates on assets.
53+
54+
## Testing
55+
56+
``` bash
57+
cargo +nightly test
58+
```

iris_asset_exchange/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ignore build artifacts from the local tests sub-crate.
2+
/target/
3+
4+
# Ignore backup files creates by cargo fmt.
5+
**/*.rs.bk
6+
7+
# Remove Cargo.lock when creating an executable, leave it for libraries
8+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
9+
Cargo.lock

iris_asset_exchange/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
name = "iris_asset_exchange"
3+
version = "0.1.0"
4+
authors = ["Tony Riemer <driemworks@iridium.industries>"]
5+
edition = "2021"
6+
rust-version = "1.56.1"
7+
8+
[dependencies]
9+
ink_primitives = { version = "3.0.0-rc9", default-features = false }
10+
ink_metadata = { version = "3.0.0-rc9", default-features = false, features = ["derive"], optional = true }
11+
ink_env = { version = "3.0.0-rc9", default-features = false }
12+
ink_storage = { version = "3.0.0-rc9", default-features = false }
13+
ink_lang = { version = "3.0.0-rc9", default-features = false }
14+
15+
scale = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
16+
scale-info = { version = "2.0.0", default-features = false, features = ["derive"], optional = true }
17+
18+
[lib]
19+
name = "iris_asset_exchange"
20+
path = "lib.rs"
21+
crate-type = [
22+
# Used for normal contract Wasm blobs.
23+
"cdylib",
24+
]
25+
26+
[features]
27+
default = ["std"]
28+
std = [
29+
"ink_metadata/std",
30+
"ink_env/std",
31+
"ink_storage/std",
32+
"ink_primitives/std",
33+
"scale/std",
34+
"scale-info/std",
35+
]
36+
ink-as-dependency = []

iris_asset_exchange/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Iris Asset Exchange
2+
3+
The Iris Asset Exchange is a contract to act as a platform for data owners to sell access to potential data consumers.
4+
5+
## Building
6+
7+
`cargo +nightly contract build`
8+
9+
## Testing
10+
11+
`cargo +nightly test`

iris_asset_exchange/lib.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#![cfg_attr(not(feature = "std"), no_std)]
2+
3+
use ink_env::Environment;
4+
use ink_lang as ink;
5+
6+
/// Functions to interact with the Iris runtime as defined in runtime/src/lib.rs
7+
#[ink::chain_extension]
8+
pub trait Iris {
9+
type ErrorCode = IrisErr;
10+
11+
#[ink(extension = 0, returns_result = false)]
12+
fn transfer_asset(caller: ink_env::AccountId, target: ink_env::AccountId, asset_id: u32, amount: u64) -> [u8; 32];
13+
14+
#[ink(extension = 1, returns_result = false)]
15+
fn mint(caller: ink_env::AccountId, target: ink_env::AccountId, asset_id: u32, amount: u64) -> [u8; 32];
16+
17+
#[ink(extension = 2, returns_result = false)]
18+
fn lock(amount: u64) -> [u8; 32];
19+
20+
#[ink(extension = 3, returns_result = false)]
21+
fn unlock_and_transfer(target: ink_env::AccountId) -> [u8; 32];
22+
}
23+
24+
#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
25+
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
26+
pub enum IrisErr {
27+
FailTransferAsset,
28+
FailMintAssets,
29+
FailLockCurrency,
30+
FailUnlockCurrency,
31+
}
32+
33+
impl ink_env::chain_extension::FromStatusCode for IrisErr {
34+
fn from_status_code(status_code: u32) -> Result<(), Self> {
35+
match status_code {
36+
0 => Err(Self::FailTransferAsset),
37+
1 => Err(Self::FailMintAssets),
38+
2 => Err(Self::FailLockCurrency),
39+
3 => Err(Self::FailUnlockCurrency),
40+
_ => panic!("encountered unknown status code"),
41+
}
42+
}
43+
}
44+
45+
#[derive(Debug, Clone, PartialEq, Eq)]
46+
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
47+
pub enum CustomEnvironment {}
48+
49+
impl Environment for CustomEnvironment {
50+
const MAX_EVENT_TOPICS: usize =
51+
<ink_env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
52+
53+
type AccountId = <ink_env::DefaultEnvironment as Environment>::AccountId;
54+
type Balance = <ink_env::DefaultEnvironment as Environment>::Balance;
55+
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
56+
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
57+
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
58+
59+
type ChainExtension = Iris;
60+
}
61+
62+
#[ink::contract(env = crate::CustomEnvironment)]
63+
mod iris_asset_exchange {
64+
// use ink_lang as ink;
65+
use super::IrisErr;
66+
use ink_storage::traits::SpreadAllocate;
67+
68+
/// Defines the storage of our contract.
69+
///
70+
#[ink(storage)]
71+
#[derive(SpreadAllocate)]
72+
pub struct IrisAssetExchange {
73+
/// maps the owner of a token sale to the asset id and asking price
74+
registry: ink_storage::Mapping<(AccountId, u32), u64>,
75+
}
76+
77+
#[ink(event)]
78+
pub struct AssetTransferSuccess { }
79+
80+
#[ink(event)]
81+
pub struct NewTokenSaleSuccess { }
82+
83+
#[ink(event)]
84+
pub struct AssetNotRegistered { }
85+
86+
impl IrisAssetExchange {
87+
88+
/// build a new Iris Asset Exchange
89+
#[ink(constructor, payable)]
90+
pub fn new() -> Self {
91+
ink_lang::utils::initialize_contract(|_| {})
92+
}
93+
94+
/// Default constructor
95+
#[ink(constructor, payable)]
96+
pub fn default() -> Self {
97+
Self::new()
98+
}
99+
100+
/// Provide pricing for a static amount of assets.
101+
///
102+
/// This function mints new assets from an asset class owned by the caller
103+
/// and assigns them to the contract address. It adds an item to the exchange's registry,
104+
/// associating the asset id with the price determined by the caller.
105+
///
106+
/// * `asset_id`: An asset_id associated with an owned asset class
107+
/// * `amount`: The amount of assets that will be minted and provisioned to the exchange
108+
/// * `price`: The price (in OBOL) of each token
109+
///
110+
#[ink(message)]
111+
pub fn publish_sale(&mut self, asset_id: u32, amount: u64, price: u64) {
112+
let caller = self.env().caller();
113+
self.env()
114+
.extension()
115+
.mint(
116+
caller, self.env().account_id(), asset_id, amount,
117+
).map_err(|_| {});
118+
self.registry.insert((&caller, &asset_id), &price);
119+
self.env().emit_event(AssetTransferSuccess { });
120+
}
121+
122+
/// Purchase assets from the exchange.
123+
///
124+
/// This function performs the following process:
125+
/// 1. lock price*amount tokens
126+
/// 2. Transfer the asset from the contract account to the caller
127+
/// 3. unlock the locked tokens from (1) and transfer to the owner of the asset class
128+
///
129+
/// * `owner`: The owner of the asset class from which the asset to be purchased was minted
130+
/// * `asset_id`: The id of the owned asset class
131+
/// * `amount`: The amount of assets to purchase
132+
///
133+
#[ink(message)]
134+
pub fn purchase_tokens(&mut self, owner: AccountId, asset_id: u32, amount: u64) {
135+
let caller = self.env().caller();
136+
// calculate total cost
137+
if let Some(price) = self.registry.get((&owner, &asset_id)) {
138+
let total_cost = amount * price;
139+
// caller locks total_cost
140+
self.env().extension().lock(total_cost).map_err(|_| {});
141+
// contract grants tokens to caller
142+
self.env()
143+
.extension()
144+
.transfer_asset(
145+
self.env().account_id(), caller, asset_id, amount,
146+
).map_err(|_| {});
147+
// caller send tokens to owner
148+
self.env().extension().unlock_and_transfer(owner).map_err(|_| {});
149+
self.env().emit_event(AssetTransferSuccess { });
150+
} else {
151+
self.env().emit_event(AssetNotRegistered { });
152+
}
153+
}
154+
}
155+
156+
/// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
157+
#[cfg(test)]
158+
mod tests {
159+
/// Imports all the definitions from the outer scope so we can use them here.
160+
use super::*;
161+
use ink_lang as ink;
162+
163+
/// We test if the default constructor does its job.
164+
#[ink::test]
165+
fn default_works() {
166+
let _iris_asset_exchange = IrisAssetExchange::default();
167+
// assert_eq!(iris_asset_exchange.registry.len(), [0; 32]);
168+
}
169+
170+
// #[ink::test]
171+
// fn chain_extension_works() {
172+
// // given
173+
// struct MockedExtension;
174+
// impl ink_env::test::ChainExtension for MockedExtension {
175+
// /// The static function id of the chain extension.
176+
// fn func_id(&self) -> u32 {
177+
// 1101
178+
// }
179+
180+
// /// The chain extension is called with the given input.
181+
// ///
182+
// /// Returns an error code and may fill the `output` buffer with a
183+
// /// SCALE encoded result. The error code is taken from the
184+
// /// `ink_env::chain_extension::FromStatusCode` implementation for
185+
// /// `RandomReadErr`.
186+
// fn call(&mut self, _input: &[u8], output: &mut Vec<u8>) -> u32 {
187+
// let ret: [u8; 32] = [1; 32];
188+
// scale::Encode::encode_to(&ret, output);
189+
// 0
190+
// }
191+
// }
192+
// ink_env::test::register_chain_extension(MockedExtension);
193+
// let mut rand_extension = RandExtension::default();
194+
// assert_eq!(rand_extension.get(), [0; 32]);
195+
196+
// // when
197+
// rand_extension.update([0_u8; 32]).expect("update must work");
198+
199+
// // then
200+
// assert_eq!(rand_extension.get(), [1; 32]);
201+
// }
202+
}
203+
}

0 commit comments

Comments
 (0)