From 4e30439671fb6a716fe5316003a65b8c39608f9c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 22 Jul 2025 23:06:52 -0300 Subject: [PATCH 01/20] added leasing precompile --- pallets/admin-utils/src/lib.rs | 2 + precompiles/src/leasing.rs | 166 +++++++++++++++++++++++++++++++++ precompiles/src/lib.rs | 8 +- 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 precompiles/src/leasing.rs diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index e4d7b225e7..1be45caaee 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -131,6 +131,8 @@ pub mod pallet { Crowdloan, /// Pure proxy precompile PureProxy, + /// Leasing precompile + Leasing, } #[pallet::type_value] diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs new file mode 100644 index 0000000000..8073680486 --- /dev/null +++ b/precompiles/src/leasing.rs @@ -0,0 +1,166 @@ +use core::marker::PhantomData; + +use fp_evm::{ExitError, PrecompileFailure}; +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_evm::AddressMapping; +use pallet_evm::PrecompileHandle; +use precompile_utils::{EvmResult, solidity::Codec}; +use sp_core::{ByteArray, H256}; +use sp_runtime::{ + Percent, + traits::{Dispatchable, UniqueSaturatedInto}, +}; +use subtensor_runtime_common::NetUid; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +pub struct LeasingPrecompile(PhantomData); + +impl PrecompileExt for LeasingPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_crowdloan::Config, + R::AccountId: From<[u8; 32]> + ByteArray, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ + const INDEX: u64 = 2058; +} + +#[precompile_utils::precompile] +impl LeasingPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_crowdloan::Config, + R::AccountId: From<[u8; 32]> + ByteArray, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, +{ + #[precompile::public("getLease(uint32)")] + #[precompile::view] + fn get_lease(_handle: &mut impl PrecompileHandle, lease_id: u32) -> EvmResult { + let lease = + pallet_subtensor::SubnetLeases::::get(lease_id).ok_or(PrecompileFailure::Error { + exit_status: ExitError::InvalidRange, + })?; + + Ok(LeaseInfo { + beneficiary: H256::from_slice(lease.beneficiary.as_slice()), + coldkey: H256::from_slice(lease.coldkey.as_slice()), + hotkey: H256::from_slice(lease.hotkey.as_slice()), + emissions_share: lease.emissions_share.deconstruct(), + has_end_block: lease.end_block.is_some(), + end_block: lease + .end_block + .map(|b| b.unique_saturated_into()) + .unwrap_or(0), + netuid: lease.netuid.into(), + cost: lease.cost, + }) + } + + #[precompile::public("getContributorShare(uint32,bytes32)")] + #[precompile::view] + fn get_contributor_share( + _handle: &mut impl PrecompileHandle, + lease_id: u32, + contributor: H256, + ) -> EvmResult<(u128, u128)> { + let contributor = R::AccountId::from(contributor.0); + let share = pallet_subtensor::SubnetLeaseShares::::get(lease_id, contributor); + + Ok((share.int().to_bits(), share.frac().to_bits())) + } + + #[precompile::public("getLeaseIdForSubnet(uint32)")] + #[precompile::view] + fn get_lease_id_for_subnet(_handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { + let lease_id = pallet_subtensor::SubnetUidToLeaseId::::get(NetUid::from(netuid)).ok_or( + PrecompileFailure::Error { + exit_status: ExitError::InvalidRange, + }, + )?; + + Ok(lease_id.into()) + } + + #[precompile::public( + "registerLeasedNetworkThroughCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)" + )] + #[precompile::payable] + fn register_leased_network_through_crowdloan( + handle: &mut impl PrecompileHandle, + crowdloan_deposit: u64, + crowdloan_min_contribution: u64, + crowdloan_cap: u64, + crowdloan_end: u32, + leasing_emissions_share: u8, + has_leasing_end_block: bool, + leasing_end_block: u32, + ) -> EvmResult<()> { + let who = handle.caller_account_id::(); + + let leasing_end_block = if has_leasing_end_block { + Some(leasing_end_block.into()) + } else { + None + }; + + let leasing_call = { + let call = pallet_subtensor::Call::::register_leased_network { + emissions_share: Percent::from_percent(leasing_emissions_share), + end_block: leasing_end_block, + }; + let system_call: ::RuntimeCall = call.into(); + Box::new(system_call.into()) + }; + + let crowdloan_call = pallet_crowdloan::Call::::create { + deposit: crowdloan_deposit, + min_contribution: crowdloan_min_contribution, + cap: crowdloan_cap, + end: crowdloan_end.into(), + call: Some(leasing_call), + target_address: None, + }; + + handle.try_dispatch_runtime_call::(crowdloan_call, RawOrigin::Signed(who)) + } + + #[precompile::public("terminateLease(uint32,bytes32)")] + #[precompile::payable] + fn terminate_lease( + handle: &mut impl PrecompileHandle, + lease_id: u32, + hotkey: H256, + ) -> EvmResult<()> { + let who = handle.caller_account_id::(); + let hotkey = R::AccountId::from(hotkey.0); + let call = pallet_subtensor::Call::::terminate_lease { lease_id, hotkey }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(who)) + } +} + +#[derive(Codec)] +struct LeaseInfo { + beneficiary: H256, + coldkey: H256, + hotkey: H256, + emissions_share: u8, + has_end_block: bool, + end_block: u32, + netuid: u16, + cost: u64, +} diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index 9eab384e3d..bff7e14f73 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -28,6 +28,7 @@ use crate::balance_transfer::*; use crate::crowdloan::*; use crate::ed25519::*; use crate::extensions::*; +use crate::leasing::*; use crate::metagraph::*; use crate::neuron::*; use crate::sr25519::*; @@ -42,6 +43,7 @@ mod balance_transfer; mod crowdloan; mod ed25519; mod extensions; +mod leasing; mod metagraph; mod neuron; mod pure_proxy; @@ -105,7 +107,7 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 21] { + pub fn used_addresses() -> [H160; 22] { [ hash(1), hash(2), @@ -128,6 +130,7 @@ where hash(AlphaPrecompile::::INDEX), hash(CrowdloanPrecompile::::INDEX), hash(PureProxyPrecompile::::INDEX), + hash(LeasingPrecompile::::INDEX), ] } } @@ -211,6 +214,9 @@ where a if a == hash(PureProxyPrecompile::::INDEX) => { PureProxyPrecompile::::try_execute::(handle, PrecompileEnum::PureProxy) } + a if a == hash(LeasingPrecompile::::INDEX) => { + LeasingPrecompile::::try_execute::(handle, PrecompileEnum::Leasing) + } _ => None, } } From 546bafab6b69559ca8db435f1122cbd06c0e9c3b Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 22 Jul 2025 23:07:04 -0300 Subject: [PATCH 02/20] added solidity + abi for leasing precompile --- precompiles/src/solidity/leasing.abi | 172 +++++++++++++++++++++++++++ precompiles/src/solidity/leasing.sol | 70 +++++++++++ 2 files changed, 242 insertions(+) create mode 100644 precompiles/src/solidity/leasing.abi create mode 100644 precompiles/src/solidity/leasing.sol diff --git a/precompiles/src/solidity/leasing.abi b/precompiles/src/solidity/leasing.abi new file mode 100644 index 0000000000..1be1b0a600 --- /dev/null +++ b/precompiles/src/solidity/leasing.abi @@ -0,0 +1,172 @@ +[ + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "contributor", + "type": "bytes32" + } + ], + "name": "getContributorShare", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + } + ], + "name": "getLease", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "beneficiary", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "emissions_share", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "has_end_block", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "end_block", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "netuid", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "cost", + "type": "uint64" + } + ], + "internalType": "struct LeaseInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "netuid", + "type": "uint16" + } + ], + "name": "getLeaseIdForSubnet", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "crowdloanDeposit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanMinContribution", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanCap", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "crowdloanEnd", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "leasingEmissionsShare", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "hasLeasingEndBlock", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "leasingEndBlock", + "type": "uint32" + } + ], + "name": "registerLeasedNetworkThroughCrowdloan", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "terminateLease", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/precompiles/src/solidity/leasing.sol b/precompiles/src/solidity/leasing.sol new file mode 100644 index 0000000000..daecb4879d --- /dev/null +++ b/precompiles/src/solidity/leasing.sol @@ -0,0 +1,70 @@ +pragma solidity ^0.8.0; + +address constant ILEASING_ADDRESS = 0x0000000000000000000000000000000000000810; + +interface ILeasing { + /** + * @dev Retrieves the lease info for a given lease id. + * @param leaseId The id of the lease to get info for. + * @return The lease info. + */ + function getLease(uint32 leaseId) external view returns (LeaseInfo memory); + + /** + * @dev Retrieves the contributor share for a given lease id and contributor. + * The share is returned as a tuple of two uint128 values, where the first value + * is the integer part and the second value is the fractional part. + * @param leaseId The id of the lease to get contributor share for. + * @param contributor The contributor to get share for. + * @return The contributor share. + */ + function getContributorShare(uint32 leaseId, bytes32 contributor) + external + view + returns (uint128, uint128); + + /** + * @dev Retrieves the lease id for a given subnet. + * @param netuid The subnet to get lease id for. + * @return The lease id. + */ + function getLeaseIdForSubnet(uint16 netuid) external view returns (uint32); + + /** + * @dev Registers a leased network through a crowdloan. + * @param crowdloanDeposit The deposit from the creator. + * @param crowdloanMinContribution The minimum contribution required to contribute to the crowdloan. + * @param crowdloanCap The maximum amount of funds that can be raised. + * @param crowdloanEnd The block number at which the crowdloan will end. + * @param leasingEmissionsShare The share of the emissions that the contributors will receive. + * @param hasLeasingEndBlock Whether the lease has an end block. + * @param leasingEndBlock The block number at which the lease will end. + */ + function registerLeasedNetworkThroughCrowdloan( + uint64 crowdloanDeposit, + uint64 crowdloanMinContribution, + uint64 crowdloanCap, + uint32 crowdloanEnd, + uint8 leasingEmissionsShare, + bool hasLeasingEndBlock, + uint32 leasingEndBlock + ) external payable; + + /** + * @dev Terminates a lease and transfers the ownership to the beneficiary. + * @param leaseId The id of the lease to terminate. + * @param hotkey The hotkey of beneficiary, it must be owned by the beneficiary coldkey. + */ + function terminateLease(uint32 leaseId, bytes32 hotkey) external payable; +} + +struct LeaseInfo { + bytes32 beneficiary; + bytes32 coldkey; + bytes32 hotkey; + uint8 emissions_share; + bool has_end_block; + uint32 end_block; + uint16 netuid; + uint64 cost; +} From 2f90e8faad493fdefab3391f8f75a27429c8e340 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 14:35:15 -0300 Subject: [PATCH 03/20] fix abi --- evm-tests/src/contracts/leasing.ts | 174 +++++++++++++++++++++++++++ precompiles/src/solidity/leasing.abi | 2 +- precompiles/src/solidity/leasing.sol | 6 +- 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 evm-tests/src/contracts/leasing.ts diff --git a/evm-tests/src/contracts/leasing.ts b/evm-tests/src/contracts/leasing.ts new file mode 100644 index 0000000000..3c569ce889 --- /dev/null +++ b/evm-tests/src/contracts/leasing.ts @@ -0,0 +1,174 @@ +export const ILEASING_ADDRESS = "0x000000000000000000000000000000000000080b"; + +export const ILeasingABI = [ + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "contributor", + "type": "bytes32" + } + ], + "name": "getContributorShare", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + } + ], + "name": "getLease", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "beneficiary", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "emissions_share", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "has_end_block", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "end_block", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "netuid", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "cost", + "type": "uint64" + } + ], + "internalType": "struct LeaseInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "netuid", + "type": "uint16" + } + ], + "name": "getLeaseIdForSubnet", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "crowdloanDeposit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanMinContribution", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanCap", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "crowdloanEnd", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "leasingEmissionsShare", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "hasLeasingEndBlock", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "leasingEndBlock", + "type": "uint32" + } + ], + "name": "createLeaseCrowdloan", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "leaseId", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "terminateLease", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +]; \ No newline at end of file diff --git a/precompiles/src/solidity/leasing.abi b/precompiles/src/solidity/leasing.abi index 1be1b0a600..cde3a8a2cf 100644 --- a/precompiles/src/solidity/leasing.abi +++ b/precompiles/src/solidity/leasing.abi @@ -146,7 +146,7 @@ "type": "uint32" } ], - "name": "registerLeasedNetworkThroughCrowdloan", + "name": "createLeaseCrowdloan", "outputs": [], "stateMutability": "payable", "type": "function" diff --git a/precompiles/src/solidity/leasing.sol b/precompiles/src/solidity/leasing.sol index daecb4879d..484b6d9f07 100644 --- a/precompiles/src/solidity/leasing.sol +++ b/precompiles/src/solidity/leasing.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.0; -address constant ILEASING_ADDRESS = 0x0000000000000000000000000000000000000810; +address constant ILEASING_ADDRESS = 0x000000000000000000000000000000000000080b; interface ILeasing { /** @@ -31,7 +31,7 @@ interface ILeasing { function getLeaseIdForSubnet(uint16 netuid) external view returns (uint32); /** - * @dev Registers a leased network through a crowdloan. + * @dev Create a lease crowdloan. * @param crowdloanDeposit The deposit from the creator. * @param crowdloanMinContribution The minimum contribution required to contribute to the crowdloan. * @param crowdloanCap The maximum amount of funds that can be raised. @@ -40,7 +40,7 @@ interface ILeasing { * @param hasLeasingEndBlock Whether the lease has an end block. * @param leasingEndBlock The block number at which the lease will end. */ - function registerLeasedNetworkThroughCrowdloan( + function createLeaseCrowdloan( uint64 crowdloanDeposit, uint64 crowdloanMinContribution, uint64 crowdloanCap, From bca6d001e87d7f3ece89509450891dc7f1d8177d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 14:35:26 -0300 Subject: [PATCH 04/20] change precompile index --- precompiles/src/leasing.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index 8073680486..c2c4951461 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -1,3 +1,4 @@ +use alloc::{boxed::Box, string::String}; use core::marker::PhantomData; use fp_evm::{ExitError, PrecompileFailure}; @@ -30,7 +31,7 @@ where + Dispatchable, ::AddressMapping: AddressMapping, { - const INDEX: u64 = 2058; + const INDEX: u64 = 2059; } #[precompile_utils::precompile] @@ -83,7 +84,7 @@ where Ok((share.int().to_bits(), share.frac().to_bits())) } - #[precompile::public("getLeaseIdForSubnet(uint32)")] + #[precompile::public("getLeaseIdForSubnet(uint16)")] #[precompile::view] fn get_lease_id_for_subnet(_handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { let lease_id = pallet_subtensor::SubnetUidToLeaseId::::get(NetUid::from(netuid)).ok_or( @@ -96,10 +97,10 @@ where } #[precompile::public( - "registerLeasedNetworkThroughCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)" + "createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)" )] #[precompile::payable] - fn register_leased_network_through_crowdloan( + fn create_lease_crowdloan( handle: &mut impl PrecompileHandle, crowdloan_deposit: u64, crowdloan_min_contribution: u64, From b7b14519b59f53b6b77e22b685c1834ba9c14ff5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 14:36:00 -0300 Subject: [PATCH 05/20] make crowdloan precompile flexible for end block --- evm-tests/test/crowdloan.precompile.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evm-tests/test/crowdloan.precompile.test.ts b/evm-tests/test/crowdloan.precompile.test.ts index 7abd54d617..314e19e82d 100644 --- a/evm-tests/test/crowdloan.precompile.test.ts +++ b/evm-tests/test/crowdloan.precompile.test.ts @@ -36,12 +36,13 @@ describe("Test Crowdloan precompile", () => { it("gets an existing crowdloan created on substrate side", async () => { const nextId = await api.query.Crowdloan.NextCrowdloanId.getValue(); + const end = await api.query.System.Number.getValue() + 100; await api.tx.Crowdloan.create({ deposit: BigInt(15_000_000_000), // 15 TAO min_contribution: BigInt(1_000_000_000), // 1 TAO cap: BigInt(100_000_000_000), // 100 TAO - end: 1000, + end, target_address: undefined, call: api.tx.System.remark({ remark: Binary.fromText("foo") }).decodedCall }).signAndSubmit(alice); @@ -110,12 +111,13 @@ describe("Test Crowdloan precompile", () => { it("contributes/withdraws to a crowdloan created on substrate side", async () => { const nextId = await api.query.Crowdloan.NextCrowdloanId.getValue(); const deposit = BigInt(15_000_000_000); // 15 TAO + const end = await api.query.System.Number.getValue() + 100; await api.tx.Crowdloan.create({ deposit, min_contribution: BigInt(1_000_000_000), // 1 TAO cap: BigInt(100_000_000_000), // 100 TAO - end: 1000, + end, target_address: undefined, call: api.tx.System.remark({ remark: Binary.fromText("foo") }).decodedCall }).signAndSubmit(alice); From 3bfe80cd3a9ce56dff92d6cee41abddcf8afffb9 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 14:36:35 -0300 Subject: [PATCH 06/20] add test for leasing precompile for retrieval/creation --- evm-tests/test/leasing.precompile.test.ts | 126 ++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 evm-tests/test/leasing.precompile.test.ts diff --git a/evm-tests/test/leasing.precompile.test.ts b/evm-tests/test/leasing.precompile.test.ts new file mode 100644 index 0000000000..b4faec55ce --- /dev/null +++ b/evm-tests/test/leasing.precompile.test.ts @@ -0,0 +1,126 @@ +import { PublicClient } from "viem"; +import { ETH_LOCAL_URL } from "../src/config"; +import { generateRandomEthersWallet, getPublicClient } from "../src/utils"; +import { ethers } from "ethers"; +import { TypedApi } from "polkadot-api"; +import { devnet } from "@polkadot-api/descriptors"; +import { getAliceSigner, getBobSigner, getDevnetApi, waitForFinalizedBlock } from "../src/substrate"; +import { forceSetBalanceToEthAddress } from "../src/subtensor"; +import { decodeAddress } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { ILEASING_ADDRESS, ILeasingABI } from "../src/contracts/leasing"; +import { ICROWDLOAN_ADDRESS, ICrowdloanABI } from "../src/contracts/crowdloan"; +import { assert } from "chai"; +import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"; + +describe("Test Leasing precompile", () => { + let publicClient: PublicClient; + let api: TypedApi; + + const alice = getAliceSigner(); + const bob = getBobSigner(); + const wallet1 = generateRandomEthersWallet(); + const wallet2 = generateRandomEthersWallet(); + + const crowdloanContract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet1); + const leaseContract = new ethers.Contract(ILEASING_ADDRESS, ILeasingABI, wallet1); + + before(async () => { + publicClient = await getPublicClient(ETH_LOCAL_URL); + api = await getDevnetApi(); + + await forceSetBalanceToEthAddress(api, wallet1.address); + await forceSetBalanceToEthAddress(api, wallet2.address); + }); + + it("gets an existing lease created on substrate side", async () => { + const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); + const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO + const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanEnd = await api.query.System.Number.getValue() + 100; + const leaseEmissionsShare = 15; + const leaseEnd = await api.query.System.Number.getValue() + 5000; + + await api.tx.Crowdloan.create({ + deposit: crowdloanDeposit, + min_contribution: BigInt(1_000_000_000), // 1 TAO + cap: crowdloanCap, + end: crowdloanEnd, + target_address: undefined, + call: api.tx.SubtensorModule.register_leased_network({ + emissions_share: leaseEmissionsShare, + end_block: leaseEnd, + }).decodedCall + }).signAndSubmit(alice); + + await api.tx.Crowdloan.contribute({ + crowdloan_id: nextCrowdloanId, + amount: crowdloanCap - crowdloanDeposit, + }).signAndSubmit(bob); + + await waitForFinalizedBlock(api, crowdloanEnd); + + const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); + await api.tx.Crowdloan.finalize({ crowdloan_id: nextCrowdloanId }).signAndSubmit(alice); + + const lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); + const leaseInfo = await leaseContract.getLease(nextLeaseId); + + assert.isDefined(lease); + assert.equal(leaseInfo[0], u8aToHex(decodeAddress(lease.beneficiary))); + assert.equal(leaseInfo[1], u8aToHex(decodeAddress(lease.coldkey))); + assert.equal(leaseInfo[2], u8aToHex(decodeAddress(lease.hotkey))); + assert.equal(leaseInfo[3], lease.emissions_share); + assert.equal(leaseInfo[4], true); //has_end_block + assert.equal(leaseInfo[5], lease.end_block); + assert.equal(leaseInfo[6], lease.netuid); + assert.equal(leaseInfo[7], lease.cost); + }) + + it("registers a new leased network through a crowdloan and retrieves the lease", async () => { + const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); + const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO + const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO + const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanEnd = await api.query.System.Number.getValue() + 100; + const leasingEmissionsShare = 15; + const leasingEndBlock = await api.query.System.Number.getValue() + 5000; + + let tx = await leaseContract.createLeaseCrowdloan( + crowdloanDeposit, + crowdloanMinContribution, + crowdloanCap, + crowdloanEnd, + leasingEmissionsShare, + true, // has_leasing_end_block + leasingEndBlock + ); + await tx.wait(); + + const crowdloanContract2 = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet2); + tx = await crowdloanContract2.contribute(nextCrowdloanId, crowdloanCap - crowdloanDeposit); + await tx.wait(); + + await waitForFinalizedBlock(api, crowdloanEnd); + + const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); + tx = await crowdloanContract.finalize(nextCrowdloanId); + await tx.wait(); + + const lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); + assert.isDefined(lease); + assert.equal(lease.beneficiary, convertH160ToSS58(wallet1.address)); + assert.equal(lease.emissions_share, leasingEmissionsShare); + assert.equal(lease.end_block, leasingEndBlock); + + const leaseInfo = await leaseContract.getLease(nextLeaseId); + assert.equal(leaseInfo[0], u8aToHex(decodeAddress(lease.beneficiary))); + assert.equal(leaseInfo[1], u8aToHex(decodeAddress(lease.coldkey))); + assert.equal(leaseInfo[2], u8aToHex(decodeAddress(lease.hotkey))); + assert.equal(leaseInfo[3], lease.emissions_share); + assert.equal(leaseInfo[4], true); // has_end_block + assert.equal(leaseInfo[5], lease.end_block); + assert.equal(leaseInfo[6], lease.netuid); + assert.equal(leaseInfo[7], lease.cost); + }); +}) \ No newline at end of file From 5fb44ceddfd3a4a6820c1dcda4803bab275fc633 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 18:26:19 -0300 Subject: [PATCH 07/20] add precompile view assertions --- evm-tests/test/leasing.precompile.test.ts | 72 ++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/evm-tests/test/leasing.precompile.test.ts b/evm-tests/test/leasing.precompile.test.ts index b4faec55ce..94b4e92252 100644 --- a/evm-tests/test/leasing.precompile.test.ts +++ b/evm-tests/test/leasing.precompile.test.ts @@ -10,8 +10,10 @@ import { decodeAddress } from "@polkadot/util-crypto"; import { u8aToHex } from "@polkadot/util"; import { ILEASING_ADDRESS, ILeasingABI } from "../src/contracts/leasing"; import { ICROWDLOAN_ADDRESS, ICrowdloanABI } from "../src/contracts/crowdloan"; +import { ISTAKING_ADDRESS, IStakingABI } from "../src/contracts/staking"; import { assert } from "chai"; -import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"; +import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58, } from "../src/address-utils"; +import { raoToEth, tao } from "../src/balance-math"; describe("Test Leasing precompile", () => { let publicClient: PublicClient; @@ -21,9 +23,11 @@ describe("Test Leasing precompile", () => { const bob = getBobSigner(); const wallet1 = generateRandomEthersWallet(); const wallet2 = generateRandomEthersWallet(); + const wallet3 = generateRandomEthersWallet(); const crowdloanContract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet1); const leaseContract = new ethers.Contract(ILEASING_ADDRESS, ILeasingABI, wallet1); + const stakingContract = new ethers.Contract(ISTAKING_ADDRESS, IStakingABI, wallet1); before(async () => { publicClient = await getPublicClient(ETH_LOCAL_URL); @@ -31,9 +35,10 @@ describe("Test Leasing precompile", () => { await forceSetBalanceToEthAddress(api, wallet1.address); await forceSetBalanceToEthAddress(api, wallet2.address); + await forceSetBalanceToEthAddress(api, wallet3.address); }); - it("gets an existing lease created on substrate side", async () => { + it("gets an existing lease created on substrate side, its subnet id and its contributor shares", async () => { const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO @@ -75,6 +80,16 @@ describe("Test Leasing precompile", () => { assert.equal(leaseInfo[5], lease.end_block); assert.equal(leaseInfo[6], lease.netuid); assert.equal(leaseInfo[7], lease.cost); + + const leaseId = await leaseContract.getLeaseIdForSubnet(lease.netuid); + assert.equal(leaseId, nextLeaseId); + + // Bob has some share and alice share is 0 because she is the beneficiary + // and beneficiary share is dynamic based on other contributors shares + const aliceShare = await leaseContract.getContributorShare(nextLeaseId, alice.publicKey) + assert.deepEqual(aliceShare, [BigInt(0), BigInt(0)]); + const bobShare = await leaseContract.getContributorShare(nextLeaseId, bob.publicKey) + assert.notDeepEqual(bobShare, [BigInt(0), BigInt(0)]); }) it("registers a new leased network through a crowdloan and retrieves the lease", async () => { @@ -122,5 +137,58 @@ describe("Test Leasing precompile", () => { assert.equal(leaseInfo[5], lease.end_block); assert.equal(leaseInfo[6], lease.netuid); assert.equal(leaseInfo[7], lease.cost); + + const leaseId = await leaseContract.getLeaseIdForSubnet(lease.netuid); + assert.equal(leaseId, nextLeaseId); + + // Bob has some share and alice share is 0 because she is the beneficiary + // and beneficiary share is dynamic based on other contributors shares + const contributor1 = await leaseContract.getContributorShare(nextLeaseId, convertH160ToPublicKey(wallet1.address)) + assert.deepEqual(contributor1, [BigInt(0), BigInt(0)]); + const contributor2 = await leaseContract.getContributorShare(nextLeaseId, convertH160ToPublicKey(wallet2.address)) + assert.notDeepEqual(contributor2, [BigInt(0), BigInt(0)]); }); + + // it("terminates a lease", async () => { + // const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); + // const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO + // const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO + // const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + // const crowdloanEnd = await api.query.System.Number.getValue() + 100; + // const leasingEmissionsShare = 15; + // const leasingEndBlock = await api.query.System.Number.getValue() + 200; + + // let tx = await leaseContract.createLeaseCrowdloan( + // crowdloanDeposit, + // crowdloanMinContribution, + // crowdloanCap, + // crowdloanEnd, + // leasingEmissionsShare, + // true, // has_leasing_end_block + // leasingEndBlock + // ); + // await tx.wait(); + + // const crowdloanContract2 = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet2); + // tx = await crowdloanContract2.contribute(nextCrowdloanId, crowdloanCap - crowdloanDeposit); + // await tx.wait(); + + // await waitForFinalizedBlock(api, crowdloanEnd); + + // const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); + // tx = await crowdloanContract.finalize(nextCrowdloanId); + // await tx.wait(); + + // await waitForFinalizedBlock(api, leasingEndBlock); + + // // // Associate wallet3 with wallet1 as a hotkey + // tx = await stakingContract.addStake(bob.publicKey, 0, { value: raoToEth(tao(20)) }); + // await tx.wait(); + + // tx = await leaseContract.terminateLease(nextLeaseId, bob.publicKey); + // await tx.wait(); + + // // const lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); + // // assert.isUndefined(lease); + // }); }) \ No newline at end of file From 06db80f0a9eaab82d3f73aef9256646f4ff9b767 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 19:32:25 -0300 Subject: [PATCH 08/20] added terminateLease test --- evm-tests/test/leasing.precompile.test.ts | 102 ++++++++++++---------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/evm-tests/test/leasing.precompile.test.ts b/evm-tests/test/leasing.precompile.test.ts index 94b4e92252..05c57d0fec 100644 --- a/evm-tests/test/leasing.precompile.test.ts +++ b/evm-tests/test/leasing.precompile.test.ts @@ -10,10 +10,9 @@ import { decodeAddress } from "@polkadot/util-crypto"; import { u8aToHex } from "@polkadot/util"; import { ILEASING_ADDRESS, ILeasingABI } from "../src/contracts/leasing"; import { ICROWDLOAN_ADDRESS, ICrowdloanABI } from "../src/contracts/crowdloan"; -import { ISTAKING_ADDRESS, IStakingABI } from "../src/contracts/staking"; +import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron"; import { assert } from "chai"; -import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58, } from "../src/address-utils"; -import { raoToEth, tao } from "../src/balance-math"; +import { convertH160ToPublicKey, convertH160ToSS58 } from "../src/address-utils"; describe("Test Leasing precompile", () => { let publicClient: PublicClient; @@ -27,7 +26,7 @@ describe("Test Leasing precompile", () => { const crowdloanContract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet1); const leaseContract = new ethers.Contract(ILEASING_ADDRESS, ILeasingABI, wallet1); - const stakingContract = new ethers.Contract(ISTAKING_ADDRESS, IStakingABI, wallet1); + const neuronContract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet1); before(async () => { publicClient = await getPublicClient(ETH_LOCAL_URL); @@ -35,7 +34,9 @@ describe("Test Leasing precompile", () => { await forceSetBalanceToEthAddress(api, wallet1.address); await forceSetBalanceToEthAddress(api, wallet2.address); - await forceSetBalanceToEthAddress(api, wallet3.address); + + await neuronContract.burnedRegister(1, convertH160ToPublicKey(wallet3.address)); + await forceSetBalanceToEthAddress(api, wallet1.address); }); it("gets an existing lease created on substrate side, its subnet id and its contributor shares", async () => { @@ -149,46 +150,53 @@ describe("Test Leasing precompile", () => { assert.notDeepEqual(contributor2, [BigInt(0), BigInt(0)]); }); - // it("terminates a lease", async () => { - // const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); - // const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO - // const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO - // const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO - // const crowdloanEnd = await api.query.System.Number.getValue() + 100; - // const leasingEmissionsShare = 15; - // const leasingEndBlock = await api.query.System.Number.getValue() + 200; - - // let tx = await leaseContract.createLeaseCrowdloan( - // crowdloanDeposit, - // crowdloanMinContribution, - // crowdloanCap, - // crowdloanEnd, - // leasingEmissionsShare, - // true, // has_leasing_end_block - // leasingEndBlock - // ); - // await tx.wait(); - - // const crowdloanContract2 = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet2); - // tx = await crowdloanContract2.contribute(nextCrowdloanId, crowdloanCap - crowdloanDeposit); - // await tx.wait(); - - // await waitForFinalizedBlock(api, crowdloanEnd); - - // const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); - // tx = await crowdloanContract.finalize(nextCrowdloanId); - // await tx.wait(); - - // await waitForFinalizedBlock(api, leasingEndBlock); - - // // // Associate wallet3 with wallet1 as a hotkey - // tx = await stakingContract.addStake(bob.publicKey, 0, { value: raoToEth(tao(20)) }); - // await tx.wait(); - - // tx = await leaseContract.terminateLease(nextLeaseId, bob.publicKey); - // await tx.wait(); - - // // const lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); - // // assert.isUndefined(lease); - // }); + it("terminates a lease", async () => { + const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); + const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO + const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO + const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanEnd = await api.query.System.Number.getValue() + 100; + const leasingEmissionsShare = 15; + const leasingEndBlock = await api.query.System.Number.getValue() + 200; + + let tx = await leaseContract.createLeaseCrowdloan( + crowdloanDeposit, + crowdloanMinContribution, + crowdloanCap, + crowdloanEnd, + leasingEmissionsShare, + true, // has_leasing_end_block + leasingEndBlock + ); + await tx.wait(); + + const crowdloanContract2 = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet2); + tx = await crowdloanContract2.contribute(nextCrowdloanId, crowdloanCap - crowdloanDeposit); + await tx.wait(); + + await waitForFinalizedBlock(api, crowdloanEnd); + + const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); + tx = await crowdloanContract.finalize(nextCrowdloanId); + await tx.wait(); + + await waitForFinalizedBlock(api, leasingEndBlock); + + let lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); + assert.isDefined(lease); + const netuid = lease.netuid; + + const hotkey = convertH160ToPublicKey(wallet3.address); + tx = await leaseContract.terminateLease(nextLeaseId, hotkey); + await tx.wait(); + + lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); + assert.isUndefined(lease); + + // Ensure that the subnet ownership has been transferred + const ownerColdkey = await api.query.SubtensorModule.SubnetOwner.getValue(netuid); + const ownerHotkey = await api.query.SubtensorModule.SubnetOwnerHotkey.getValue(netuid); + assert.equal(ownerColdkey, convertH160ToSS58(wallet1.address)); + assert.equal(ownerHotkey, convertH160ToSS58(wallet3.address)); + }); }) \ No newline at end of file From 3420f1f1359d07e06b0845f11fbed0aef791f3f0 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 23 Jul 2025 19:33:19 -0300 Subject: [PATCH 09/20] refacto crypto helpers --- evm-tests/src/substrate.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/evm-tests/src/substrate.ts b/evm-tests/src/substrate.ts index 2970d9d4bb..2a627a2c0c 100644 --- a/evm-tests/src/substrate.ts +++ b/evm-tests/src/substrate.ts @@ -1,15 +1,13 @@ -import * as assert from "assert"; import { devnet, MultiAddress } from '@polkadot-api/descriptors'; -import { createClient, TypedApi, Transaction, PolkadotSigner, Binary } from 'polkadot-api'; -import { getWsProvider } from 'polkadot-api/ws-provider/web'; +import { TypedApi, Transaction, PolkadotSigner, Binary } from 'polkadot-api'; import { sr25519CreateDerive } from "@polkadot-labs/hdkd" -import { convertPublicKeyToSs58 } from "../src/address-utils" import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy, KeyPair } from "@polkadot-labs/hdkd-helpers" import { getPolkadotSigner } from "polkadot-api/signer" import { randomBytes } from 'crypto'; import { Keyring } from '@polkadot/keyring'; import { SS58_PREFIX, TX_TIMEOUT } from "./config"; import { getClient } from "./setup" + let api: TypedApi | undefined = undefined // define url string as type to extend in the future @@ -24,26 +22,36 @@ export async function getDevnetApi() { return api } -export function getAlice() { +export function getKeypairFromPath(path: string) { const entropy = mnemonicToEntropy(DEV_PHRASE) const miniSecret = entropyToMiniSecret(entropy) const derive = sr25519CreateDerive(miniSecret) - const hdkdKeyPair = derive("//Alice") + const hdkdKeyPair = derive(path) return hdkdKeyPair } -export function getAliceSigner() { - const alice = getAlice() +export const getAlice = () => getKeypairFromPath("//Alice") +export const getBob = () => getKeypairFromPath("//Bob") +export const getCharlie = () => getKeypairFromPath("//Charlie") +export const getDave = () => getKeypairFromPath("//Dave") + +export function getSignerFromPath(path: string) { + const keypair = getKeypairFromPath(path) const polkadotSigner = getPolkadotSigner( - alice.publicKey, + keypair.publicKey, "Sr25519", - alice.sign, + keypair.sign, ) return polkadotSigner } +export const getAliceSigner = () => getSignerFromPath("//Alice") +export const getBobSigner = () => getSignerFromPath("//Bob") +export const getCharlieSigner = () => getSignerFromPath("//Charlie") +export const getDaveSigner = () => getSignerFromPath("//Dave") + export function getRandomSubstrateSigner() { const keypair = getRandomSubstrateKeypair(); return getSignerFromKeypair(keypair) From a382cc866df98ba774fcd60a42f1ac4dee5082ff Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 11:50:08 -0300 Subject: [PATCH 10/20] fix flaky test due to lock cost increase --- evm-tests/test/leasing.precompile.test.ts | 40 +++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/evm-tests/test/leasing.precompile.test.ts b/evm-tests/test/leasing.precompile.test.ts index 05c57d0fec..53bbbf8334 100644 --- a/evm-tests/test/leasing.precompile.test.ts +++ b/evm-tests/test/leasing.precompile.test.ts @@ -18,23 +18,30 @@ describe("Test Leasing precompile", () => { let publicClient: PublicClient; let api: TypedApi; + let wallet1: ethers.Wallet; + let wallet2: ethers.Wallet; + let wallet3: ethers.Wallet; + let leaseContract: ethers.Contract; + let crowdloanContract: ethers.Contract; + let neuronContract: ethers.Contract; + const alice = getAliceSigner(); const bob = getBobSigner(); - const wallet1 = generateRandomEthersWallet(); - const wallet2 = generateRandomEthersWallet(); - const wallet3 = generateRandomEthersWallet(); - - const crowdloanContract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet1); - const leaseContract = new ethers.Contract(ILEASING_ADDRESS, ILeasingABI, wallet1); - const neuronContract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet1); - before(async () => { + beforeEach(async () => { publicClient = await getPublicClient(ETH_LOCAL_URL); api = await getDevnetApi(); + wallet1 = generateRandomEthersWallet(); + wallet2 = generateRandomEthersWallet(); + wallet3 = generateRandomEthersWallet(); + leaseContract = new ethers.Contract(ILEASING_ADDRESS, ILeasingABI, wallet1); + crowdloanContract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, wallet1); + neuronContract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet1); + await forceSetBalanceToEthAddress(api, wallet1.address); await forceSetBalanceToEthAddress(api, wallet2.address); - + await forceSetBalanceToEthAddress(api, wallet3.address); await neuronContract.burnedRegister(1, convertH160ToPublicKey(wallet3.address)); await forceSetBalanceToEthAddress(api, wallet1.address); }); @@ -42,10 +49,10 @@ describe("Test Leasing precompile", () => { it("gets an existing lease created on substrate side, its subnet id and its contributor shares", async () => { const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO - const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanCap = BigInt(10_000_000_000_000); // 10000 TAO const crowdloanEnd = await api.query.System.Number.getValue() + 100; const leaseEmissionsShare = 15; - const leaseEnd = await api.query.System.Number.getValue() + 5000; + const leaseEnd = await api.query.System.Number.getValue() + 300; await api.tx.Crowdloan.create({ deposit: crowdloanDeposit, @@ -91,16 +98,16 @@ describe("Test Leasing precompile", () => { assert.deepEqual(aliceShare, [BigInt(0), BigInt(0)]); const bobShare = await leaseContract.getContributorShare(nextLeaseId, bob.publicKey) assert.notDeepEqual(bobShare, [BigInt(0), BigInt(0)]); - }) + }); it("registers a new leased network through a crowdloan and retrieves the lease", async () => { const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO - const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanCap = BigInt(10_000_000_000_000); // 10000 TAO const crowdloanEnd = await api.query.System.Number.getValue() + 100; const leasingEmissionsShare = 15; - const leasingEndBlock = await api.query.System.Number.getValue() + 5000; + const leasingEndBlock = await api.query.System.Number.getValue() + 300; let tx = await leaseContract.createLeaseCrowdloan( crowdloanDeposit, @@ -154,7 +161,7 @@ describe("Test Leasing precompile", () => { const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO - const crowdloanCap = BigInt(2_000_000_000_000); // 2000 TAO + const crowdloanCap = BigInt(10_000_000_000_000); // 10000 TAO const crowdloanEnd = await api.query.System.Number.getValue() + 100; const leasingEmissionsShare = 15; const leasingEndBlock = await api.query.System.Number.getValue() + 200; @@ -186,8 +193,7 @@ describe("Test Leasing precompile", () => { assert.isDefined(lease); const netuid = lease.netuid; - const hotkey = convertH160ToPublicKey(wallet3.address); - tx = await leaseContract.terminateLease(nextLeaseId, hotkey); + tx = await leaseContract.terminateLease(nextLeaseId, convertH160ToPublicKey(wallet3.address)); await tx.wait(); lease = await api.query.SubtensorModule.SubnetLeases.getValue(nextLeaseId); From b4fa1ef75776316aedb65643d8e1c83896d46803 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 11:56:34 -0300 Subject: [PATCH 11/20] change leasing precompile index + update abi/sol --- evm-tests/src/contracts/leasing.ts | 90 ++++++++++++++-------------- precompiles/src/leasing.rs | 2 +- precompiles/src/solidity/leasing.abi | 86 +++++++++++++------------- precompiles/src/solidity/leasing.sol | 2 +- 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/evm-tests/src/contracts/leasing.ts b/evm-tests/src/contracts/leasing.ts index 3c569ce889..80321c7462 100644 --- a/evm-tests/src/contracts/leasing.ts +++ b/evm-tests/src/contracts/leasing.ts @@ -1,6 +1,49 @@ -export const ILEASING_ADDRESS = "0x000000000000000000000000000000000000080b"; +export const ILEASING_ADDRESS = "0x000000000000000000000000000000000000080a"; export const ILeasingABI = [ + { + "inputs": [ + { + "internalType": "uint64", + "name": "crowdloanDeposit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanMinContribution", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanCap", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "crowdloanEnd", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "leasingEmissionsShare", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "hasLeasingEndBlock", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "leasingEndBlock", + "type": "uint32" + } + ], + "name": "createLeaseCrowdloan", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -110,49 +153,6 @@ export const ILeasingABI = [ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "crowdloanDeposit", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanMinContribution", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanCap", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "crowdloanEnd", - "type": "uint32" - }, - { - "internalType": "uint8", - "name": "leasingEmissionsShare", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "hasLeasingEndBlock", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "leasingEndBlock", - "type": "uint32" - } - ], - "name": "createLeaseCrowdloan", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, { "inputs": [ { @@ -171,4 +171,4 @@ export const ILeasingABI = [ "stateMutability": "payable", "type": "function" } -]; \ No newline at end of file +] \ No newline at end of file diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index c2c4951461..5faeb15136 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -31,7 +31,7 @@ where + Dispatchable, ::AddressMapping: AddressMapping, { - const INDEX: u64 = 2059; + const INDEX: u64 = 2058; } #[precompile_utils::precompile] diff --git a/precompiles/src/solidity/leasing.abi b/precompiles/src/solidity/leasing.abi index cde3a8a2cf..88115ee29c 100644 --- a/precompiles/src/solidity/leasing.abi +++ b/precompiles/src/solidity/leasing.abi @@ -1,4 +1,47 @@ [ + { + "inputs": [ + { + "internalType": "uint64", + "name": "crowdloanDeposit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanMinContribution", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "crowdloanCap", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "crowdloanEnd", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "leasingEmissionsShare", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "hasLeasingEndBlock", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "leasingEndBlock", + "type": "uint32" + } + ], + "name": "createLeaseCrowdloan", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -108,49 +151,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "crowdloanDeposit", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanMinContribution", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanCap", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "crowdloanEnd", - "type": "uint32" - }, - { - "internalType": "uint8", - "name": "leasingEmissionsShare", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "hasLeasingEndBlock", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "leasingEndBlock", - "type": "uint32" - } - ], - "name": "createLeaseCrowdloan", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, { "inputs": [ { diff --git a/precompiles/src/solidity/leasing.sol b/precompiles/src/solidity/leasing.sol index 484b6d9f07..184b832a10 100644 --- a/precompiles/src/solidity/leasing.sol +++ b/precompiles/src/solidity/leasing.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.0; -address constant ILEASING_ADDRESS = 0x000000000000000000000000000000000000080b; +address constant ILEASING_ADDRESS = 0x000000000000000000000000000000000000080a; interface ILeasing { /** From 1f9e35e9dd561a74a285b61868ec9294f58f9ccc Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 12:15:09 -0300 Subject: [PATCH 12/20] cargo fmt --- precompiles/src/leasing.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index 5faeb15136..9a549d347e 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -96,9 +96,7 @@ where Ok(lease_id.into()) } - #[precompile::public( - "createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)" - )] + #[precompile::public("createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)")] #[precompile::payable] fn create_lease_crowdloan( handle: &mut impl PrecompileHandle, From 6f08a52f1530f458a54fb07efddb69edf972924f Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 12:16:32 -0300 Subject: [PATCH 13/20] bump spec version to 298 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 624ee0edf8..229f359c96 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -218,7 +218,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 297, + spec_version: 298, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 52dc3922a10b864f243a821058b502cd36a77f72 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 12:16:40 -0300 Subject: [PATCH 14/20] fix clippy warning --- precompiles/src/leasing.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index 9a549d347e..a0e8131437 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -98,6 +98,7 @@ where #[precompile::public("createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)")] #[precompile::payable] + #[allow(clippy::too_many_arguments)] fn create_lease_crowdloan( handle: &mut impl PrecompileHandle, crowdloan_deposit: u64, From 918fb41577538fea5ca099e07d670925f723c9a9 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 24 Jul 2025 12:32:24 -0300 Subject: [PATCH 15/20] more explicit failure --- precompiles/src/crowdloan.rs | 4 ++-- precompiles/src/leasing.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/precompiles/src/crowdloan.rs b/precompiles/src/crowdloan.rs index cedcf24af3..8403b8262d 100644 --- a/precompiles/src/crowdloan.rs +++ b/precompiles/src/crowdloan.rs @@ -48,7 +48,7 @@ where ) -> EvmResult { let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id).ok_or( PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, + exit_status: ExitError::Other("Crowdloan not found".into()), }, )?; @@ -80,7 +80,7 @@ where let coldkey = R::AccountId::from(coldkey.0); let contribution = pallet_crowdloan::Contributions::::get(crowdloan_id, coldkey).ok_or( PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, + exit_status: ExitError::Other("Crowdloan or contribution not found".into()), }, )?; diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index a0e8131437..c4d64f428a 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -53,7 +53,7 @@ where fn get_lease(_handle: &mut impl PrecompileHandle, lease_id: u32) -> EvmResult { let lease = pallet_subtensor::SubnetLeases::::get(lease_id).ok_or(PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, + exit_status: ExitError::Other("Lease not found".into()), })?; Ok(LeaseInfo { @@ -89,7 +89,7 @@ where fn get_lease_id_for_subnet(_handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { let lease_id = pallet_subtensor::SubnetUidToLeaseId::::get(NetUid::from(netuid)).ok_or( PrecompileFailure::Error { - exit_status: ExitError::InvalidRange, + exit_status: ExitError::Other("Lease not found for netuid".into()), }, )?; From 078020c37b4fcd360a4021d90b85815fc3e75791 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 24 Jul 2025 16:18:08 -0400 Subject: [PATCH 16/20] make rust cache much cachier --- .github/workflows/cargo-audit.yml | 3 +- .github/workflows/check-devnet.yml | 3 +- .github/workflows/check-finney.yml | 2 +- .github/workflows/check-rust.yml | 21 +++ .github/workflows/check-testnet.yml | 3 +- .github/workflows/run-benchmarks.yml | 237 ++++++++++++++------------- .github/workflows/try-runtime.yml | 3 +- 7 files changed, 149 insertions(+), 123 deletions(-) diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index aa8fb7c9f3..5ced91529c 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -27,7 +27,8 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "cargo-audit" + key: cargo-audit + cache-on-failure: true - name: Install cargo-audit run: cargo install --force cargo-audit diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index b6c20beee0..8e04b4a588 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -26,7 +26,8 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "spec-version" + key: try-runtime + cache-on-failure: true - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/check-finney.yml b/.github/workflows/check-finney.yml index 448e53ee1f..4a99df7868 100644 --- a/.github/workflows/check-finney.yml +++ b/.github/workflows/check-finney.yml @@ -26,7 +26,7 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "spec-version" + key: try-runtime - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 0fae77fa33..6206f7efa2 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -40,6 +40,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: cargo-fmt + cache-on-failure: true - name: cargo fmt run: cargo +nightly fmt --check --all @@ -61,6 +64,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: clippy-default-features + cache-on-failure: true - name: cargo clippy --workspace --all-targets -- -D warnings run: cargo clippy --workspace --all-targets -- -D warnings @@ -83,6 +89,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: lints + cache-on-failure: true - name: check lints run: | @@ -107,6 +116,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: clippy-all-features + cache-on-failure: true - name: cargo clippy --workspace --all-targets --all-features -- -D warnings run: cargo clippy --workspace --all-targets --all-features -- -D warnings @@ -129,6 +141,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: cargo-test + cache-on-failure: true - name: cargo test --workspace --all-features run: cargo test --workspace --all-features @@ -151,6 +166,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: cargo-fix + cache-on-failure: true - name: cargo fix --workspace run: | @@ -177,6 +195,9 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 + with: + key: zepter + cache-on-failure: true - name: Install Zepter run: cargo install --locked -q zepter && zepter --version diff --git a/.github/workflows/check-testnet.yml b/.github/workflows/check-testnet.yml index 8a59036d98..919af25637 100644 --- a/.github/workflows/check-testnet.yml +++ b/.github/workflows/check-testnet.yml @@ -26,7 +26,8 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "spec-version" + key: try-runtime + cache-on-failure: true - name: Install substrate-spec-version run: cargo install substrate-spec-version diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index 4e882351f7..fbdcd8e5c0 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -19,123 +19,124 @@ jobs: runs-on: Benchmarking env: - SKIP_BENCHMARKS: '0' - AUTO_COMMIT_WEIGHTS: '1' + SKIP_BENCHMARKS: "0" + AUTO_COMMIT_WEIGHTS: "1" steps: - # ────────────────────────────────────────────────────────────────── - - name: Check out PR branch - if: ${{ env.SKIP_BENCHMARKS != '1' }} - uses: actions/checkout@v4 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - name: Install GitHub CLI - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - sudo apt-get update - sudo apt-get install -y gh - echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token - - # (1) — first skip‑label check - - name: Check skip label - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label found — skipping benchmarks." - echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" - fi - - - name: Install system dependencies - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - sudo apt-get update - sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler - - # (2) - - name: Check skip label - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label found — skipping benchmarks." - echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" - fi - - - name: Install Rust toolchain - if: ${{ env.SKIP_BENCHMARKS != '1' }} - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - - # (3) - - name: Check skip label - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label found — skipping benchmarks." - echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" - fi - - - name: Cache Rust build - if: ${{ env.SKIP_BENCHMARKS != '1' }} - uses: Swatinem/rust-cache@v2 - with: - key: bench-${{ hashFiles('**/Cargo.lock') }} - - # (4) - - name: Check skip label - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label found — skipping benchmarks." - echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" - fi - - - name: Build node with benchmarks - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - cargo build --profile production -p node-subtensor --features runtime-benchmarks - - # (5) - - name: Check skip label - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label found — skipping benchmarks." - echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" - fi - - - name: Run & validate benchmarks - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - chmod +x scripts/benchmark_action.sh - scripts/benchmark_action.sh - - # (6) — final check after run - - name: Check skip label after run - if: ${{ env.SKIP_BENCHMARKS != '1' }} - run: | - labels=$(gh pr view ${{ github.event.pull_request.number }} \ - --repo "${{ github.repository }}" \ - --json labels --jq '.labels[].name') - if echo "$labels" | grep -q "skip-validate-benchmarks"; then - echo "skip-validate-benchmarks label was found — but benchmarks already ran." - fi + # ────────────────────────────────────────────────────────────────── + - name: Check out PR branch + if: ${{ env.SKIP_BENCHMARKS != '1' }} + uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - name: Install GitHub CLI + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + sudo apt-get update + sudo apt-get install -y gh + echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token + + # (1) — first skip‑label check + - name: Check skip label + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label found — skipping benchmarks." + echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" + fi + + - name: Install system dependencies + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + sudo apt-get update + sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler + + # (2) + - name: Check skip label + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label found — skipping benchmarks." + echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" + fi + + - name: Install Rust toolchain + if: ${{ env.SKIP_BENCHMARKS != '1' }} + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + + # (3) + - name: Check skip label + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label found — skipping benchmarks." + echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" + fi + + - name: Cache Rust build + if: ${{ env.SKIP_BENCHMARKS != '1' }} + uses: Swatinem/rust-cache@v2 + with: + key: bench-${{ hashFiles('**/Cargo.lock') }} + cache-on-failure: true + + # (4) + - name: Check skip label + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label found — skipping benchmarks." + echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" + fi + + - name: Build node with benchmarks + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + cargo build --profile production -p node-subtensor --features runtime-benchmarks + + # (5) + - name: Check skip label + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label found — skipping benchmarks." + echo "SKIP_BENCHMARKS=1" >> "$GITHUB_ENV" + fi + + - name: Run & validate benchmarks + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + chmod +x scripts/benchmark_action.sh + scripts/benchmark_action.sh + + # (6) — final check after run + - name: Check skip label after run + if: ${{ env.SKIP_BENCHMARKS != '1' }} + run: | + labels=$(gh pr view ${{ github.event.pull_request.number }} \ + --repo "${{ github.repository }}" \ + --json labels --jq '.labels[].name') + if echo "$labels" | grep -q "skip-validate-benchmarks"; then + echo "skip-validate-benchmarks label was found — but benchmarks already ran." + fi diff --git a/.github/workflows/try-runtime.yml b/.github/workflows/try-runtime.yml index 7577529d92..13383feb9b 100644 --- a/.github/workflows/try-runtime.yml +++ b/.github/workflows/try-runtime.yml @@ -60,7 +60,8 @@ jobs: - name: Utilize Shared Rust Cache uses: Swatinem/rust-cache@v2 with: - key: "try-runtime" + key: try-runtime + cache-on-failure: true - name: Run Try Runtime Checks uses: "paritytech/try-runtime-gha@v0.1.0" From e368a4954ee0700858d8daac748c25b85d7fdd19 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 24 Jul 2025 16:25:10 -0400 Subject: [PATCH 17/20] save-if: true --- .github/workflows/check-devnet.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index 8e04b4a588..867102a315 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -28,6 +28,7 @@ jobs: with: key: try-runtime cache-on-failure: true + save-if: true - name: Install substrate-spec-version run: cargo install substrate-spec-version From 6842594729061a1ed782ea079907391fa442494e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 24 Jul 2025 17:02:42 -0400 Subject: [PATCH 18/20] bump spec version to 298 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 624ee0edf8..229f359c96 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -218,7 +218,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 297, + spec_version: 298, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 56dbe7084e09d0fe264bf6cbacc49cfe2400bf10 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 28 Jul 2025 16:06:35 -0400 Subject: [PATCH 19/20] Fix insufficient liquidity issue --- pallets/subtensor/src/tests/staking.rs | 61 ++++++++++++++++++++++++++ pallets/swap/src/pallet/impls.rs | 15 ++++--- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index c7447b51cc..7d0fc0bfd5 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -763,6 +763,67 @@ fn test_add_stake_insufficient_liquidity() { }); } +/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_insufficient_liquidity_one_side_ok --exact --show-output +#[test] +fn test_add_stake_insufficient_liquidity_one_side_ok() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let amount_staked = DefaultMinStake::::get() * 10; + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount_staked); + + // Set the liquidity at lowest possible value so that all staking requests fail + let reserve_alpha = u64::from(mock::SwapMinimumReserve::get()); + let reserve_tao = u64::from(mock::SwapMinimumReserve::get()) - 1; + mock::setup_reserves(netuid, reserve_tao, reserve_alpha.into()); + + // Check the error + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + amount_staked + )); + }); +} + +/// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_insufficient_liquidity_one_side_fail --exact --show-output +#[test] +fn test_add_stake_insufficient_liquidity_one_side_fail() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let amount_staked = DefaultMinStake::::get() * 10; + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount_staked); + + // Set the liquidity at lowest possible value so that all staking requests fail + let reserve_alpha = u64::from(mock::SwapMinimumReserve::get()) - 1; + let reserve_tao = u64::from(mock::SwapMinimumReserve::get()); + mock::setup_reserves(netuid, reserve_tao, reserve_alpha.into()); + + // Check the error + assert_noop!( + SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + amount_staked + ), + Error::::InsufficientLiquidity + ); + }); +} + #[test] fn test_remove_stake_insufficient_liquidity() { new_test_ext(1).execute_with(|| { diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 2e0a28c978..32e0ec563c 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -465,12 +465,17 @@ impl Pallet { limit_sqrt_price: SqrtPrice, drop_fees: bool, ) -> Result> { - ensure!( - T::SubnetInfo::tao_reserve(netuid.into()) >= T::MinimumReserve::get().get() - && u64::from(T::SubnetInfo::alpha_reserve(netuid.into())) + match order_type { + OrderType::Buy => ensure!( + u64::from(T::SubnetInfo::alpha_reserve(netuid.into())) >= T::MinimumReserve::get().get(), - Error::::ReservesTooLow - ); + Error::::ReservesTooLow + ), + OrderType::Sell => ensure!( + T::SubnetInfo::tao_reserve(netuid.into()) >= T::MinimumReserve::get().get(), + Error::::ReservesTooLow + ), + } Self::maybe_initialize_v3(netuid)?; From c329448dea3a37e1a4496598abb98c0420fa8e61 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 29 Jul 2025 16:23:55 -0400 Subject: [PATCH 20/20] Fix v3 emissions on low prices + tests --- .../subtensor/src/coinbase/run_coinbase.rs | 95 ++++++++++----- pallets/subtensor/src/staking/stake_utils.rs | 5 +- pallets/subtensor/src/subnets/registration.rs | 10 +- pallets/subtensor/src/tests/children.rs | 1 + pallets/subtensor/src/tests/coinbase.rs | 113 ++++++++++++------ pallets/subtensor/src/tests/staking.rs | 6 +- pallets/subtensor/src/tests/staking2.rs | 1 + 7 files changed, 153 insertions(+), 78 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index aaccb1f749..4551365848 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -53,6 +53,7 @@ impl Pallet { let mut tao_in: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); + let mut is_subsidized: BTreeMap = BTreeMap::new(); // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. @@ -62,23 +63,50 @@ impl Pallet { let moving_price_i: U96F32 = Self::get_moving_alpha_price(*netuid_i); log::debug!("moving_price_i: {:?}", moving_price_i); // Emission is price over total. - let mut tao_in_i: U96F32 = block_emission + let default_tao_in_i: U96F32 = block_emission .saturating_mul(moving_price_i) .checked_div(total_moving_prices) .unwrap_or(asfloat!(0.0)); - log::debug!("tao_in_i: {:?}", tao_in_i); + log::debug!("default_tao_in_i: {:?}", default_tao_in_i); // Get alpha_emission total let alpha_emission_i: U96F32 = asfloat!( Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into()) .unwrap_or(0) ); log::debug!("alpha_emission_i: {:?}", alpha_emission_i); + // Get initial alpha_in - let alpha_in_i: U96F32 = tao_in_i - .checked_div(price_i) - .unwrap_or(alpha_emission_i) - .min(alpha_emission_i); + let alpha_in_i: U96F32; + let mut tao_in_i: U96F32; + let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or( + U96F32::saturating_from_num(block_emission), + U96F32::saturating_from_num(0.0), + ); + if price_i < tao_in_ratio { + tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission)); + alpha_in_i = alpha_emission_i; + let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); + // Difference becomes buy. + let buy_swap_result = Self::swap_tao_for_alpha( + *netuid_i, + tou64!(difference_tao), + T::SwapInterface::max_price(), + true, + ); + if let Ok(buy_swap_result_ok) = buy_swap_result { + let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); + SubnetAlphaOut::::mutate(*netuid_i, |total| { + *total = total.saturating_sub(bought_alpha); + }); + } + is_subsidized.insert(*netuid_i, true); + } else { + tao_in_i = default_tao_in_i; + alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); + is_subsidized.insert(*netuid_i, false); + } log::debug!("alpha_in_i: {:?}", alpha_in_i); + // Get alpha_out. let alpha_out_i = alpha_emission_i; // Only emit TAO if the subnetwork allows registration. @@ -101,15 +129,15 @@ impl Pallet { // This operation changes the pool liquidity each block. for netuid_i in subnets_to_emit_to.iter() { // Inject Alpha in. - let alpha_in_i: AlphaCurrency = - tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let alpha_in_i = + AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))); SubnetAlphaInEmission::::insert(*netuid_i, alpha_in_i); SubnetAlphaIn::::mutate(*netuid_i, |total| { *total = total.saturating_add(alpha_in_i); }); // Injection Alpha out. - let alpha_out_i: AlphaCurrency = - tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let alpha_out_i = + AlphaCurrency::from(tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)))); SubnetAlphaOutEmission::::insert(*netuid_i, alpha_out_i); SubnetAlphaOut::::mutate(*netuid_i, |total| { *total = total.saturating_add(alpha_out_i); @@ -182,29 +210,30 @@ impl Pallet { let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha); log::debug!("pending_alpha: {:?}", pending_alpha); // Sell root emission through the pool (do not pay fees) - let swap_result = Self::swap_alpha_for_tao( - *netuid_i, - tou64!(root_alpha).into(), - T::SwapInterface::min_price(), - true, - ); - if let Ok(ok_result) = swap_result { - let root_tao: u64 = ok_result.amount_paid_out; - - log::debug!("root_tao: {:?}", root_tao); - // Accumulate alpha emission in pending. - PendingAlphaSwapped::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(root_alpha).into()); - }); - // Accumulate alpha emission in pending. - PendingEmission::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(pending_alpha).into()); - }); - // Accumulate root divs for subnet. - PendingRootDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(root_tao); - }); + let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); + if !subsidized { + let swap_result = Self::swap_alpha_for_tao( + *netuid_i, + tou64!(root_alpha).into(), + T::SwapInterface::min_price(), + true, + ); + if let Ok(ok_result) = swap_result { + let root_tao: u64 = ok_result.amount_paid_out; + // Accumulate root divs for subnet. + PendingRootDivs::::mutate(*netuid_i, |total| { + *total = total.saturating_add(root_tao); + }); + } } + // Accumulate alpha emission in pending. + PendingAlphaSwapped::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(root_alpha).into()); + }); + // Accumulate alpha emission in pending. + PendingEmission::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(pending_alpha).into()); + }); } // --- 7 Update moving prices after using them in the emission calculation. @@ -236,7 +265,7 @@ impl Pallet { PendingEmission::::insert(netuid, AlphaCurrency::ZERO); // Get and drain the subnet pending root divs. - let pending_tao: u64 = PendingRootDivs::::get(netuid); + let pending_tao = PendingRootDivs::::get(netuid); PendingRootDivs::::insert(netuid, 0); // Get this amount as alpha that was swapped for pending root divs. diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 38027f489f..a244f9f5a7 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -629,6 +629,7 @@ impl Pallet { netuid: NetUid, tao: u64, price_limit: u64, + drop_fees: bool, ) -> Result { // Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic) let mechanism_id: u16 = SubnetMechanism::::get(netuid); @@ -638,7 +639,7 @@ impl Pallet { OrderType::Buy, tao, price_limit, - false, + drop_fees, false, )?; let alpha_decrease = @@ -821,7 +822,7 @@ impl Pallet { set_limit: bool, ) -> Result { // Swap the tao to alpha. - let swap_result = Self::swap_tao_for_alpha(netuid, tao, price_limit)?; + let swap_result = Self::swap_tao_for_alpha(netuid, tao, price_limit, false)?; ensure!(swap_result.amount_paid_out > 0, Error::::AmountTooLow); diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index d999c299e5..67631d3fc1 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -141,9 +141,13 @@ impl Pallet { Self::remove_balance_from_coldkey_account(&coldkey, registration_cost)?; // Tokens are swapped and then burned. - let burned_alpha = - Self::swap_tao_for_alpha(netuid, actual_burn_amount, T::SwapInterface::max_price())? - .amount_paid_out; + let burned_alpha = Self::swap_tao_for_alpha( + netuid, + actual_burn_amount, + T::SwapInterface::max_price(), + false, + )? + .amount_paid_out; SubnetAlphaOut::::mutate(netuid, |total| { *total = total.saturating_sub(burned_alpha.into()) }); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 12991094e5..24b858628a 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -3031,6 +3031,7 @@ fn test_parent_child_chain_emission() { netuid, total_tao.to_num::(), ::SwapInterface::max_price(), + false, ) .unwrap() .amount_paid_out, diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 03fdf4227e..2ed25b4c10 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -10,6 +10,7 @@ use pallet_subtensor_swap::position::PositionId; use sp_core::U256; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::AlphaCurrency; +use subtensor_swap_interface::SwapHandler; #[allow(clippy::arithmetic_side_effects)] fn close(value: u64, target: u64, eps: u64) { @@ -158,22 +159,49 @@ fn test_coinbase_tao_issuance_different_prices() { let emission: u64 = 100_000_000; add_network(netuid1, 1, 0); add_network(netuid2, 1, 0); + + // Setup prices 0.1 and 0.2 + let initial_tao: u64 = 100_000_u64; + let initial_alpha1: u64 = initial_tao * 10; + let initial_alpha2: u64 = initial_tao * 5; + mock::setup_reserves(netuid1, initial_tao, initial_alpha1.into()); + mock::setup_reserves(netuid2, initial_tao, initial_alpha2.into()); + + // Force the swap to initialize + SubtensorModule::swap_tao_for_alpha(netuid1, 0, 1_000_000_000_000, false).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid2, 0, 1_000_000_000_000, false).unwrap(); + // Make subnets dynamic. SubnetMechanism::::insert(netuid1, 1); SubnetMechanism::::insert(netuid2, 1); + // Set subnet prices. SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); + // Assert initial TAO reserves. - assert_eq!(SubnetTAO::::get(netuid1), 0); - assert_eq!(SubnetTAO::::get(netuid2), 0); + assert_eq!(SubnetTAO::::get(netuid1), initial_tao); + assert_eq!(SubnetTAO::::get(netuid2), initial_tao); + // Run the coinbase with the emission amount. SubtensorModule::run_coinbase(U96F32::from_num(emission)); + // Assert tao emission is split evenly. - assert_eq!(SubnetTAO::::get(netuid1), emission / 3); - assert_eq!(SubnetTAO::::get(netuid2), emission / 3 + emission / 3); - close(TotalIssuance::::get(), emission, 2); - close(TotalStake::::get(), emission, 2); + assert_abs_diff_eq!( + SubnetTAO::::get(netuid1), + initial_tao + emission / 3, + epsilon = 1, + ); + assert_abs_diff_eq!( + SubnetTAO::::get(netuid2), + initial_tao + 2 * emission / 3, + epsilon = 1, + ); + + // Prices are low => we limit tao issued (buy alpha with it) + let tao_issued = ((0.1 + 0.2) * emission as f64) as u64; + assert_abs_diff_eq!(TotalIssuance::::get(), tao_issued, epsilon = 10); + assert_abs_diff_eq!(TotalStake::::get(), emission, epsilon = 10); }); } @@ -391,17 +419,11 @@ fn test_coinbase_alpha_issuance_with_cap_trigger() { SubtensorModule::run_coinbase(U96F32::from_num(emission)); // tao_in = 333_333 // alpha_in = 333_333/price > 1_000_000_000 --> 1_000_000_000 + initial_alpha - assert_eq!( - SubnetAlphaIn::::get(netuid1), - (initial_alpha + 1_000_000_000).into() - ); + assert!(SubnetAlphaIn::::get(netuid1) < (initial_alpha + 1_000_000_000).into()); assert_eq!(SubnetAlphaOut::::get(netuid2), 1_000_000_000.into()); // tao_in = 666_666 // alpha_in = 666_666/price > 1_000_000_000 --> 1_000_000_000 + initial_alpha - assert_eq!( - SubnetAlphaIn::::get(netuid2), - (initial_alpha + 1_000_000_000).into() - ); + assert!(SubnetAlphaIn::::get(netuid2) < (initial_alpha + 1_000_000_000).into()); assert_eq!(SubnetAlphaOut::::get(netuid2), 1_000_000_000.into()); // Gets full block emission. }); } @@ -415,39 +437,56 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { let emission: u64 = 1_000_000; add_network(netuid1, 1, 0); add_network(netuid2, 1, 0); + // Make subnets dynamic. SubnetMechanism::::insert(netuid1, 1); SubnetMechanism::::insert(netuid2, 1); - // Setup prices 1000000 - let initial: u64 = 1_000; - let initial_alpha = AlphaCurrency::from(initial * 1000000); - SubnetTAO::::insert(netuid1, initial); - SubnetAlphaIn::::insert(netuid1, initial_alpha); // Make price extremely low. - SubnetTAO::::insert(netuid2, initial); - SubnetAlphaIn::::insert(netuid2, initial_alpha); // Make price extremely low. - // Set issuance to greater than 21M - SubnetAlphaOut::::insert(netuid1, AlphaCurrency::from(22_000_000_000_000_000)); // Set issuance above 21M - SubnetAlphaOut::::insert(netuid2, AlphaCurrency::from(22_000_000_000_000_000)); // Set issuance above 21M - // Set subnet prices. + + // Setup prices 0.000001 + let initial_tao: u64 = 10_000_u64; + let initial_alpha: u64 = initial_tao * 100_000_u64; + mock::setup_reserves(netuid1, initial_tao, initial_alpha.into()); + mock::setup_reserves(netuid2, initial_tao, initial_alpha.into()); + + // Enable emission + FirstEmissionBlockNumber::::insert(netuid1, 0); + FirstEmissionBlockNumber::::insert(netuid2, 0); SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); + + // Force the swap to initialize + SubtensorModule::swap_tao_for_alpha(netuid1, 0, 1_000_000_000_000, false).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid2, 0, 1_000_000_000_000, false).unwrap(); + + // Get the prices before the run_coinbase + let price_1_before = ::SwapInterface::current_alpha_price(netuid1); + let price_2_before = ::SwapInterface::current_alpha_price(netuid2); + + // Set issuance at 21M + SubnetAlphaOut::::insert(netuid1, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M + SubnetAlphaOut::::insert(netuid2, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M + // Run coinbase SubtensorModule::run_coinbase(U96F32::from_num(emission)); - // tao_in = 333_333 - // alpha_in = 333_333/price > 1_000_000_000 --> 0 + initial_alpha - assert_eq!(SubnetAlphaIn::::get(netuid1), initial_alpha); + + // Get the prices after the run_coinbase + let price_1_after = ::SwapInterface::current_alpha_price(netuid1); + let price_2_after = ::SwapInterface::current_alpha_price(netuid2); + + // AlphaIn gets decreased beacuse of a buy + assert!(u64::from(SubnetAlphaIn::::get(netuid1)) < initial_alpha); assert_eq!( - SubnetAlphaOut::::get(netuid2), - 22_000_000_000_000_000.into() + u64::from(SubnetAlphaOut::::get(netuid2)), + 21_000_000_000_000_000_u64 ); - // tao_in = 666_666 - // alpha_in = 666_666/price > 1_000_000_000 --> 0 + initial_alpha - assert_eq!(SubnetAlphaIn::::get(netuid2), initial_alpha); + assert!(u64::from(SubnetAlphaIn::::get(netuid2)) < initial_alpha); assert_eq!( - SubnetAlphaOut::::get(netuid2), - 22_000_000_000_000_000.into() + u64::from(SubnetAlphaOut::::get(netuid2)), + 21_000_000_000_000_000_u64 ); - // No emission. + + assert!(price_1_after > price_1_before); + assert!(price_2_after > price_2_before); }); } @@ -2388,7 +2427,7 @@ fn test_coinbase_v3_liquidity_update() { let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap(); let protocol_account_id = pallet_subtensor_swap::Pallet::::protocol_account_id(); let position = pallet_subtensor_swap::Positions::::get(( diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 7d0fc0bfd5..08dade7ef3 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -565,7 +565,7 @@ fn test_add_stake_partial_below_min_stake_fails() { mock::setup_reserves(netuid, amount * 10, (amount * 10).into()); // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap(); // Get the current price (should be 1.0) let current_price = @@ -3306,7 +3306,7 @@ fn test_max_amount_add_dynamic() { SubnetAlphaIn::::insert(netuid, alpha_in); // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap(); if !alpha_in.is_zero() { let expected_price = U96F32::from_num(tao_in) / U96F32::from_num(alpha_in); @@ -5705,7 +5705,7 @@ fn test_large_swap() { pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap(); + SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap(); setup_positions(netuid.into()); diff --git a/pallets/subtensor/src/tests/staking2.rs b/pallets/subtensor/src/tests/staking2.rs index 9e374095de..973ec855bf 100644 --- a/pallets/subtensor/src/tests/staking2.rs +++ b/pallets/subtensor/src/tests/staking2.rs @@ -39,6 +39,7 @@ fn test_stake_base_case() { netuid, tao_to_swap, ::SwapInterface::max_price(), + false, ) .unwrap() .amount_paid_out,