Skip to content
Draft
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
48 changes: 18 additions & 30 deletions allways/cli/swap_commands/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ def admin_group():
set-min-swap <amount_tao> Set minimum swap amount (0 = no minimum)
set-max-swap <amount_tao> Set maximum swap amount (0 = no maximum)
set-votes <count> Set required validator votes
set-recycle-address <account_id> Set address for fee recycling transfers
add-vali <hotkey> Add a validator
remove-vali <hotkey> Remove a validator
recycle-fees Recycle accumulated fees (stake + burn alpha)
recycle-fees Stake accumulated fees on-chain via chain extension
transfer-ownership <account_id> Transfer contract ownership
danger halt Halt the system (block new reservations)
danger resume Resume the system (allow new reservations)
Expand Down Expand Up @@ -411,42 +410,28 @@ def remove_vali(hotkey: str):
console.print(f'[red]Failed to remove validator: {e}[/red]\n')


@admin_group.command('set-recycle-address')
@click.argument('account_id', type=str)
def set_recycle_address(account_id: str):
"""Set the address where recycled fees are transferred.

Example:
alw admin set-recycle-address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
"""
_, wallet, _, client = get_cli_context()

console.print('\n[bold]Set Recycle Address[/bold]\n')
console.print(f' Address: {account_id}\n')

if not click.confirm('Confirm?'):
console.print('[yellow]Cancelled[/yellow]')
return

try:
with loading('Submitting transaction...'):
client.set_recycle_address(wallet=wallet, address=account_id)
console.print(f'[green]Recycle address set to {account_id}[/green]\n')
except ContractError as e:
console.print(f'[red]Failed to set recycle address: {e}[/red]\n')


@admin_group.command('recycle-fees')
def recycle_fees():
"""Transfer accumulated fees to the designated recycle address.
"""Stake accumulated fees on-chain via chain extension.

Example:
alw admin recycle-fees
"""
_, wallet, _, client = get_cli_context()

try:
accumulated = client.get_accumulated_fees()
except ContractError:
accumulated = None

if accumulated is not None and accumulated == 0:
console.print('\n[yellow]No accumulated fees to recycle[/yellow]\n')
return

fee_display = f'{from_rao(accumulated):.4f} TAO' if accumulated else 'unknown'
console.print('\n[bold]Recycle Fees[/bold]\n')
console.print(' Action: transfer all accumulated fees to recycle address\n')
console.print(f' Accumulated fees: {fee_display}')
console.print(' Action: stake fees on-chain via chain extension\n')

if not click.confirm('Confirm recycling fees?'):
console.print('[yellow]Cancelled[/yellow]')
Expand All @@ -457,7 +442,10 @@ def recycle_fees():
client.recycle_fees(wallet=wallet)
console.print('[green]Fees recycled successfully[/green]\n')
except ContractError as e:
console.print(f'[red]Failed to recycle fees: {e}[/red]\n')
console.print(f'[red]Failed to recycle fees: {e}[/red]')
if 'ContractReverted' in str(e):
console.print('[dim]Hint: treasury hotkey may not be registered on the subnet[/dim]')
console.print()


@admin_group.command('transfer-ownership')
Expand Down
3 changes: 0 additions & 3 deletions allways/cli/swap_commands/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ def _read(fn, default=None):
accumulated_fees_rao = _read(client.get_accumulated_fees)
total_recycled_rao = _read(client.get_total_recycled_fees)
owner = _read(client.get_owner)
recycle_address = _read(client.get_recycle_address, default=None)
except ContractError as e:
console.print(f'[red]Failed to read contract parameters: {e}[/red]')
return
Expand All @@ -457,8 +456,6 @@ def _read(fn, default=None):
table.add_row('Accumulated Fees', f'{from_rao(accumulated_fees_rao):.4f} TAO')
table.add_row('Total Recycled Fees', f'{from_rao(total_recycled_rao):.4f} TAO')
table.add_row('Owner', owner)
if recycle_address:
table.add_row('Recycle Address', recycle_address)

console.print(table)
console.print()
Expand Down
13 changes: 1 addition & 12 deletions allways/contract_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
'set_consensus_threshold': bytes.fromhex('c0d8ec47'),
'set_min_swap_amount': bytes.fromhex('800e1573'),
'set_max_swap_amount': bytes.fromhex('3e868f32'),
'set_recycle_address': bytes.fromhex('50dfe685'),
'set_reservation_ttl': bytes.fromhex('3143d9e3'),
'set_fee_divisor': bytes.fromhex('8832de41'),
'recycle_fees': bytes.fromhex('97756ea1'),
Expand All @@ -65,7 +64,6 @@
'get_accumulated_fees': bytes.fromhex('bf3b5d4e'),
'get_total_recycled_fees': bytes.fromhex('9910e939'),
'get_owner': bytes.fromhex('07fcd0b1'),
'get_recycle_address': bytes.fromhex('3847e06c'),
'get_pending_slash': bytes.fromhex('48c78c4a'),
'get_min_swap_amount': bytes.fromhex('fca7daa4'),
'get_max_swap_amount': bytes.fromhex('97826e04'),
Expand Down Expand Up @@ -130,7 +128,6 @@
'set_consensus_threshold': [('percent', 'u8')],
'set_min_swap_amount': [('amount', 'u128')],
'set_max_swap_amount': [('amount', 'u128')],
'set_recycle_address': [('address', 'AccountId')],
'set_reservation_ttl': [('blocks', 'u32')],
'set_fee_divisor': [('divisor', 'u128')],
'recycle_fees': [],
Expand Down Expand Up @@ -243,6 +240,7 @@ class ContractErrorKind(Enum):
24: ('HashMismatch', 'Request hash does not match computed hash'),
25: ('PendingConflict', 'A pending vote exists for a different request'),
26: ('SameChain', 'Source and destination chains must be different'),
27: ('SystemHalted', 'System is halted — no new activity allowed'),
}


Expand Down Expand Up @@ -843,9 +841,6 @@ def get_owner(self) -> str:
def get_halted(self) -> bool:
return self._read_bool('get_halted')

def get_recycle_address(self) -> str:
return self._read_account_id('get_recycle_address')

def is_validator(self, account: str) -> bool:
return self._read_bool('is_validator', {'account': account})

Expand Down Expand Up @@ -1112,12 +1107,6 @@ def set_min_swap_amount(self, wallet: bt.Wallet, amount_rao: int) -> str:
bt.logging.info(f'Min swap amount set to {amount_rao}: {tx_hash}')
return tx_hash

def set_recycle_address(self, wallet: bt.Wallet, address: str) -> str:
self._ensure_initialized()
tx_hash = self._exec_contract_raw('set_recycle_address', args={'address': address}, keypair=wallet.hotkey)
bt.logging.info(f'Recycle address set to {address}: {tx_hash}')
return tx_hash

def set_reservation_ttl(self, wallet: bt.Wallet, blocks: int) -> str:
self._ensure_initialized()
tx_hash = self._exec_contract_raw('set_reservation_ttl', args={'blocks': blocks}, keypair=wallet.hotkey)
Expand Down
7 changes: 3 additions & 4 deletions allways/metadata/allways_swap_manager.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"source": {
"hash": "0x30b3f2dc49bdb3ba3843f7c0851a8036e6b4f89e76132515d88653301a41ff15",
"hash": "0x15206c9262e680d8c0d5ce92e3b880e2d4c0c30d6e2e489c706be5d3904405cc",
"language": "ink! 5.1.1",
"compiler": "rustc 1.91.1",
"build_info": {
Expand Down Expand Up @@ -6419,9 +6419,8 @@
"variant": {}
},
"path": [
"ink_env",
"types",
"NoChainExtension"
"allways_swap_manager",
"SubtensorExtension"
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion smart-contracts/ink/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ pub enum Error {
PendingConflict,
/// Source and destination chains must be different
SameChain,
/// System is halted — no new reservations allowed
/// System is halted — no new activity allowed
SystemHalted,
}
2 changes: 1 addition & 1 deletion smart-contracts/ink/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub struct ConfigUpdated {
pub value: u128,
}

/// Event emitted when accumulated fees are recycled (transferred to recycle address)
/// Event emitted when accumulated fees are staked on-chain via chain extension
#[ink::event]
pub struct FeesRecycled {
pub tao_amount: u128,
Expand Down
63 changes: 46 additions & 17 deletions smart-contracts/ink/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,48 @@ mod events;
use types::{SwapData, SwapStatus, VoteType};
use errors::Error;

#[ink::contract]
#[ink::chain_extension(extension = 0x1000)]
pub trait SubtensorExtension {
type ErrorCode = SubtensorError;

#[ink(function = 18)]
fn add_stake_recycle(
hotkey: <CustomEnvironment as ink::env::Environment>::AccountId,
netuid: u16,
amount: u64,
) -> u64;
}

#[ink::scale_derive(Encode, Decode, TypeInfo)]
pub enum SubtensorError {
ChainExtensionFailed,
}

impl ink::env::chain_extension::FromStatusCode for SubtensorError {
fn from_status_code(status_code: u32) -> Result<(), Self> {
match status_code {
0 => Ok(()),
_ => Err(Self::ChainExtensionFailed),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[ink::scale_derive(TypeInfo)]
pub enum CustomEnvironment {}

impl ink::env::Environment for CustomEnvironment {
const MAX_EVENT_TOPICS: usize =
<ink::env::DefaultEnvironment as ink::env::Environment>::MAX_EVENT_TOPICS;
type AccountId = <ink::env::DefaultEnvironment as ink::env::Environment>::AccountId;
type Balance = <ink::env::DefaultEnvironment as ink::env::Environment>::Balance;
type Hash = <ink::env::DefaultEnvironment as ink::env::Environment>::Hash;
type Timestamp = <ink::env::DefaultEnvironment as ink::env::Environment>::Timestamp;
type BlockNumber = <ink::env::DefaultEnvironment as ink::env::Environment>::BlockNumber;
type ChainExtension = SubtensorExtension;
}

#[ink::contract(env = crate::CustomEnvironment)]
mod allways_swap_manager {
use super::*;
use events::*;
Expand All @@ -21,7 +62,6 @@ mod allways_swap_manager {
// Configuration
owner: AccountId,
treasury_hotkey: AccountId,
recycle_address: AccountId,
netuid: u16,
fulfillment_timeout_blocks: u32,
reservation_ttl: u32,
Expand Down Expand Up @@ -224,7 +264,6 @@ mod allways_swap_manager {
#[ink(constructor)]
pub fn new(
treasury_hotkey: AccountId,
recycle_address: AccountId,
netuid: u16,
fulfillment_timeout_blocks: u32,
reservation_ttl: u32,
Expand All @@ -237,7 +276,6 @@ mod allways_swap_manager {
Self {
owner: Self::env().caller(),
treasury_hotkey,
recycle_address,
netuid,
fulfillment_timeout_blocks,
reservation_ttl,
Expand Down Expand Up @@ -1082,13 +1120,6 @@ mod allways_swap_manager {
Ok(())
}

#[ink(message)]
pub fn set_recycle_address(&mut self, address: AccountId) -> Result<(), Error> {
self.ensure_owner()?;
self.recycle_address = address;
Ok(())
}

#[ink(message)]
pub fn set_reservation_ttl(&mut self, blocks: u32) -> Result<(), Error> {
self.ensure_owner()?;
Expand Down Expand Up @@ -1134,7 +1165,10 @@ mod allways_swap_manager {
return Err(Error::ZeroAmount);
}

self.env().transfer(self.recycle_address, fees)
let amount: u64 = fees.try_into().map_err(|_| Error::TransferFailed)?;
self.env()
.extension()
.add_stake_recycle(self.treasury_hotkey, self.netuid, amount)
.map_err(|_| Error::TransferFailed)?;

self.accumulated_fees = 0;
Expand Down Expand Up @@ -1222,11 +1256,6 @@ mod allways_swap_manager {
self.halted
}

#[ink(message)]
pub fn get_recycle_address(&self) -> AccountId {
self.recycle_address
}

#[ink(message)]
pub fn get_pending_slash(&self, swap_id: u64) -> Balance {
self.pending_slashes.get(swap_id).map(|(_, amount)| amount).unwrap_or(0)
Expand Down