Skip to content

Commit

Permalink
Merge pull request #1114 from interlay/kintsugi-contracts
Browse files Browse the repository at this point in the history
feat: contracts pallet on Kintsugi
  • Loading branch information
gregdhill authored Jul 26, 2023
2 parents c2a0209 + 478ffc9 commit f2f48a1
Show file tree
Hide file tree
Showing 18 changed files with 2,773 additions and 357 deletions.
828 changes: 479 additions & 349 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech//substrate",
sc-network-transactions = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
sp-consensus-beefy = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
sp-consensus-grandpa = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
pallet-message-queue = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
pallet-contracts = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
pallet-contracts-primitives = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }

[patch."https://github.com/paritytech/polkadot"]
kusama-runtime = { git = "https://github.com/paritytech//polkadot", branch = "release-v0.9.42" }
Expand Down
66 changes: 66 additions & 0 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Smart Contracts

This directory contains example smart contracts. The contracts are written in Rust using the [ink! framework](https://use.ink/). ink! is an [Embedded Domain Specific Language (EDSL)](https://wiki.haskell.org/Embedded_domain_specific_language) that uses attribute macros within standard Rust to define smart contracts.

## Prerequisites

Install `cargo-contract`:

```bash
cargo install cargo-contract
```

## Usage

### Existing contracts

Change into the contracts directory:

```bash
cd hello_world
```

Build the contracts:

```bash
cargo contract build
```

Run the tests:

```bash
cargo test
```

### New contracts

Create a new contract:

```bash
cargo contract new <contract-name>
```

Write the contract in the generated `lib.rs` file.

Build and run tests like above.

## Deploy

Deploy the contract to the local testnet:

```bash
cargo contract upload
```

## Interact

Interact with the contract on the local testnet:

```bash
cargo contract call
```

## Useful resources

- ink! documentation: https://use.ink/getting-started/setup
- hackathon template: https://github.com/scio-labs/inkathon
32 changes: 32 additions & 0 deletions contracts/btc_swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "btc_swap"
version = "0.1.0"
authors = ["Interlay Ltd"]
edition = "2021"

[workspace]

[dependencies]
ink = { version = "4.2.0", 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 }

bitcoin = { path = "../../crates/bitcoin", default-features = false }

[dev-dependencies]
ink_e2e = "4.2.0"

[lib]
path = "lib.rs"

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

use bitcoin::types::{FullTransactionProof, MerkleProof, Transaction};
use ink::{env::Environment, prelude::vec::Vec};

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum CustomEnvironment {}

impl Environment for CustomEnvironment {
const MAX_EVENT_TOPICS: usize = <ink::env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;

type AccountId = <ink::env::DefaultEnvironment as Environment>::AccountId;
type Balance = <ink::env::DefaultEnvironment as Environment>::Balance;
type Hash = <ink::env::DefaultEnvironment as Environment>::Hash;
type BlockNumber = <ink::env::DefaultEnvironment as Environment>::BlockNumber;
type Timestamp = <ink::env::DefaultEnvironment as Environment>::Timestamp;

type ChainExtension = DoSomethingInRuntime;
}

#[ink::chain_extension]
pub trait DoSomethingInRuntime {
type ErrorCode = RuntimeErr;

/// Note: this gives the operation a corresponding `func_id` (1101 in this case),
/// and the chain-side chain extension will get the `func_id` to do further operations.
#[ink(extension = 1101)]
fn get_and_verify_bitcoin_payment(full_proof: FullTransactionProof, address: Vec<u8>) -> Option<u64>;
}

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

impl ink::env::chain_extension::FromStatusCode for RuntimeErr {
fn from_status_code(status_code: u32) -> Result<(), Self> {
match status_code {
0 => Ok(()),
1 => Err(Self::SomeFailure),
_ => panic!("encountered unknown status code"),
}
}
}

/// Creates a swap contract where Alice locks DOT with a price limit in a contract that
/// Bob can acquire by sending BTC on the Bitcoin chain that Alice provided.
///
/// Note: this is a proof of concept protocol and should not be used in production due to flaws in
/// the protocol and implementation.
///
/// ## Protocol
///
/// - Alice provides a BTC address, a price limit, and a DOT amount to lock in the contract.
/// - Bob sends BTC to the address provided by Alice.
/// - Anyone (Alice, Bob, or a third party) provides a BTC transaction proof to the contract.
/// The proof triggers a DOT transfer from the contract to Bob.
#[ink::contract(env = crate::CustomEnvironment)]
mod btc_swap {
use super::*;
use bitcoin::Address as BtcAddress;
use ink::storage::Mapping;
use scale::Encode;

/// Defines the limit order contract
#[derive(scale::Decode, scale::Encode)]
#[cfg_attr(
feature = "std",
derive(Debug, PartialEq, Eq, scale_info::TypeInfo, ink::storage::traits::StorageLayout)
)]
pub struct LimitOrder {
/// The BTC address to send BTC to
/// can't store as `BtcAddress`, since `StorageLayout` is not implemented, and we can't derive it
/// due to the hashes inside it not implementing `StorageLayout`
btc_address: Vec<u8>,
/// The price limit for the BTC denoted in satoshis
min_satoshis: u64,
/// The DOT amount to lock in the contract denoted in planck
plancks: u128,
}

/// Defines the storage of your contract.
/// Add new fields to the below struct in order
/// to add new static storage fields to your contract.
#[ink(storage)]
pub struct BtcSwap {
/// Stores a mapping from an account to a limit order.
orders: ink::storage::Mapping<AccountId, LimitOrder>,
}

impl BtcSwap {
#[ink(constructor)]
pub fn new() -> Self {
let orders = Mapping::default();
Self { orders }
}

#[ink(message, payable)]
pub fn create_trade(&mut self, btc_address: BtcAddress, min_satoshis: u64) {
assert!(min_satoshis > 0);

let caller = self.env().caller();
let offer = self.env().transferred_value();

let order = LimitOrder {
btc_address: btc_address.encode(),
min_satoshis,
plancks: offer,
};
self.orders.insert(caller, &order);
}

#[ink(message)]
pub fn execute_trade(&mut self, counterparty: AccountId, full_proof: FullTransactionProof) {
let caller = self.env().caller();
let order = self.orders.get(&counterparty).unwrap();

let transferred_sats = self
.env()
.extension()
.get_and_verify_bitcoin_payment(full_proof, order.btc_address)
.unwrap()
.unwrap_or(0);

assert!(transferred_sats >= order.min_satoshis);

self.env().transfer(caller, order.plancks).unwrap();
}
}
}
Loading

0 comments on commit f2f48a1

Please sign in to comment.