Skip to content
Open
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
63 changes: 58 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false }
pallet-root-testing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false }
pallet-contracts = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false }

# NPoS
frame-election-provider-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2503-6", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This repository contains Bittensor's substrate-chain. Subtensor contains the tru
1. Runs Bittensor's [consensus mechanism](./docs/consensus.md);
2. Advertises neuron information, IPs, etc., and
3. Facilitates value transfer via TAO.
4. Supports wasm smart contract functionality via `pallet-contracts` (see [contracts documentation](./docs/contracts.md)).

## System Requirements

Expand Down
78 changes: 78 additions & 0 deletions docs/contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Smart Contracts on Subtensor

## Overview

Subtensor now supports smart contract functionality through the integration of `pallet-contracts`, enabling developers to deploy and execute WebAssembly (WASM) smart contracts on the network. Contracts are written in [ink!](https://use.ink/), a Rust-based embedded domain-specific language (eDSL) for writing smart contracts on Substrate-based chains.

## Getting Started

For general smart contract development on Subtensor, please refer to the official ink! documentation:
- [ink! Documentation](https://use.ink/docs/v5/)
- [ink! Getting Started Guide](https://use.ink/docs/v5/getting-started/setup)
- [ink! Examples](https://github.com/use-ink/ink-examples/tree/v5.x.x)

## Subtensor-Specific Features

### Chain Extension

Subtensor provides a custom chain extension that allows smart contracts to interact with Subtensor-specific functionality:

#### Available Functions

| Function ID | Name | Description | Parameters | Returns |
|------------|------|-------------|------------|---------|
| 1001 | `get_stake_info_for_hotkey_coldkey_netuid` | Query stake information | `(AccountId32, AccountId32, NetUid)` | Stake information |

Example usage in your ink! contract:
```rust
#[ink::chain_extension(extension = 0)]
pub trait SubtensorExtension {
type ErrorCode = SubtensorError;

#[ink(function = 1001)]
fn get_stake_info(
hotkey: AccountId,
coldkey: AccountId,
netuid: u16,
) -> Result<Option<StakeInfo>, SubtensorError>;
}
```

### Call Filter

For security, contracts can only dispatch a limited set of runtime calls:

**Whitelisted Calls:**
- `SubtensorModule::add_stake` - Delegate stake from a coldkey to a hotkey
- `SubtensorModule::remove_stake` - Withdraw stake from a hotkey back to the caller
- `SubtensorModule::unstake_all` - Unstake all funds associated with a hotkey
- `SubtensorModule::unstake_all_alpha` - Unstake all alpha stake from a hotkey
- `SubtensorModule::move_stake` - Move stake between hotkeys
- `SubtensorModule::transfer_stake` - Transfer stake between coldkeys (optionally across subnets)
- `SubtensorModule::swap_stake` - Swap stake allocations between subnets
- `SubtensorModule::add_stake_limit` - Delegate stake with a price limit
- `SubtensorModule::remove_stake_limit` - Withdraw staked funds with a price limit
- `SubtensorModule::swap_stake_limit` - Swap stake between subnets with a price limit
- `SubtensorModule::remove_stake_full_limit` - Fully withdraw stake subject to a price limit
- `SubtensorModule::set_coldkey_auto_stake_hotkey` - Configure the automatic stake destination for a coldkey
- `Proxy::proxy` - Execute proxy calls
- `Proxy::add_proxy` - Add a proxy relationship
- `Proxy::create_pure` - Create a pure proxy account

All other runtime calls are restricted and cannot be dispatched from contracts.

### Configuration Parameters

| Parameter | Value | Description |
|-----------|-------|-------------|
| Maximum code size | 128 KB | Maximum size of contract WASM code |
| Call stack depth | 5 frames | Maximum nested contract call depth |
| Runtime memory | 1 GB | Memory available during contract execution |
| Validator runtime memory | 2 GB | Memory available for validators |
| Transient storage | 1 MB | Maximum transient storage size |


## Additional Resources

- [cargo-contract CLI Tool](https://github.com/paritytech/cargo-contract)
- [Contracts UI](https://contracts-ui.substrate.io/)
7 changes: 7 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ pallet-commitments.workspace = true

# for prod_or_fast! macro
runtime-common.workspace = true

# Wasm smart contracts support
pallet-contracts.workspace = true

# NPoS
frame-election-provider-support = { workspace = true }
pallet-authority-discovery = { workspace = true }
Expand Down Expand Up @@ -267,6 +271,7 @@ std = [
"pallet-subtensor-swap/std",
"pallet-subtensor-swap-runtime-api/std",
"subtensor-swap-interface/std",
"pallet-contracts/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
Expand Down Expand Up @@ -301,6 +306,7 @@ runtime-benchmarks = [
"pallet-nomination-pools/runtime-benchmarks",
"pallet-offences/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
"pallet-contracts/runtime-benchmarks",

# EVM + Frontier
"pallet-ethereum/runtime-benchmarks",
Expand Down Expand Up @@ -342,6 +348,7 @@ try-runtime = [
"pallet-babe/try-runtime",
"pallet-session/try-runtime",
"pallet-staking/try-runtime",
"pallet-contracts/try-runtime",
"pallet-election-provider-multi-phase/try-runtime",
"frame-election-provider-support/try-runtime",
"pallet-authority-discovery/try-runtime",
Expand Down
52 changes: 52 additions & 0 deletions runtime/src/chain_extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use codec::Encode;
use pallet_contracts::chain_extension::{
ChainExtension, Environment, Ext, InitState, RetVal, SysConfig,
};
use sp_runtime::{AccountId32, DispatchError};
use subtensor_runtime_common::NetUid;

use crate::{Runtime, SubtensorModule};

#[derive(Default)]
pub struct SubtensorChainExtension;

impl ChainExtension<Runtime> for SubtensorChainExtension {
fn call<E: Ext>(&mut self, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
where
E::T: SysConfig,
{
let func_id = env.func_id();

match func_id {
// Function ID 1001: get_stake_info_for_hotkey_coldkey_netuid
1001 => {
let mut env = env.buf_in_buf_out();

let input: (AccountId32, AccountId32, NetUid) = env
.read_as()
.map_err(|_| DispatchError::Other("Failed to decode input parameters"))?;

let (hotkey, coldkey, netuid) = input;

let stake_info = SubtensorModule::get_stake_info_for_hotkey_coldkey_netuid(
hotkey, coldkey, netuid,
);

let encoded_result = stake_info.encode();

env.write(&encoded_result, false, None)
.map_err(|_| DispatchError::Other("Failed to write output"))?;

Ok(RetVal::Converging(0))
}
_ => {
log::error!("Called an unregistered chain extension function: {func_id}",);
Err(DispatchError::Other("Unimplemented function ID"))
}
}
}

fn enabled() -> bool {
true
}
}
Loading