Skip to content
Merged
67 changes: 64 additions & 3 deletions pallets/services/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use crate::{Call, Config, Pallet};
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use frame_support::BoundedVec;
use frame_system::RawOrigin;
use parity_scale_codec::Decode;
use parity_scale_codec::{Decode, Encode};
use scale_info::prelude::boxed::Box;
use sp_core::{H160, Pair};
use sp_core::{ByteArray, H160, crypto::Pair, ecdsa};
use sp_runtime::{KeyTypeId, Percent};
use sp_std::{prelude::*, vec};
use tangle_primitives::services::{
Expand All @@ -15,12 +15,12 @@ use tangle_primitives::services::{
};

pub type AssetId = u32;
pub type AssetIdOf<T> = <T as Config>::AssetId;
const CGGMP21_BLUEPRINT: H160 = H160([0x21; 20]);
pub const TNT: AssetId = 0;
pub const USDC: AssetId = 1;
pub const WETH: AssetId = 2;
pub const WBTC: AssetId = 3;

pub(crate) fn get_security_requirement<T: Config>(
a: T::AssetId,
p: &[u8; 2],
Expand Down Expand Up @@ -531,6 +531,67 @@ benchmarks! {
vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())].try_into().unwrap()
)

heartbeat {
const HEARTBEAT_INTERVAL_VALUE: u32 = 10;
const DUMMY_OPERATOR_ADDRESS_BYTES: [u8; 20] = [1u8; 20];

let creator: T::AccountId = mock_account_id::<T>(0u8);
let operator_account: T::AccountId = mock_account_id::<T>(1u8);
let service_requester: T::AccountId = mock_account_id::<T>(2u8);

let blueprint_id = 0u64;
let service_id = Pallet::<T>::next_service_request_id();

let mut blueprint = cggmp21_blueprint::<T>();
Pallet::<T>::create_blueprint(RawOrigin::Signed(creator.clone()).into(), blueprint.clone()).unwrap();

let operator_key = ecdsa::Pair::from_seed(&[1u8; 32]);
let operator_address = H160(DUMMY_OPERATOR_ADDRESS_BYTES);
let op_preferences = operator_preferences::<T>();
let registration_args = Vec::<Field<T::Constraints, T::AccountId>>::new();

Pallet::<T>::register(
RawOrigin::Signed(operator_account.clone()).into(),
blueprint_id,
op_preferences,
registration_args,
0u32.into()
).unwrap();

frame_system::Pallet::<T>::set_block_number(1u32.into());

Pallet::<T>::request(
RawOrigin::Signed(service_requester.clone()).into(),
None,
blueprint_id,
vec![operator_account.clone()].try_into().unwrap(),
vec![operator_account.clone()].try_into().unwrap(),
Default::default(),
Default::default(),
100u32.into(),
Asset::Custom(T::AssetId::from(USDC)),
0u32.into(),
MembershipModel::Fixed { min_operators: 1u32.into() }
).unwrap();

frame_system::Pallet::<T>::set_block_number(2u32.into());

frame_system::Pallet::<T>::set_block_number((HEARTBEAT_INTERVAL_VALUE + 2).into());

let metrics_data: Vec<u8> = vec![1,2,3];

let mut message = service_id.to_le_bytes().to_vec();
message.extend_from_slice(&blueprint_id.to_le_bytes());
message.extend_from_slice(&metrics_data);

let message_hash = sp_core::hashing::keccak_256(&message);

let signature_bytes = [0u8; 65];
let signature = ecdsa::Signature::from_raw(signature_bytes);


}: _(RawOrigin::Signed(operator_account.clone()), blueprint_id, service_id, metrics_data, signature)

// Slash an operator's stake for a service
slash {
let alice: T::AccountId = mock_account_id::<T>(1u8);
Expand Down
212 changes: 211 additions & 1 deletion pallets/services/src/functions/evm_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,216 @@ impl<T: Config> Pallet<T> {
)
}

/// Gets the heartbeat interval for a service instance by calling the blueprint's EVM contract.
///
/// This function dispatches a call to the `getHeartbeatInterval` function of the service
/// blueprint's manager contract to retrieve the heartbeat interval for a service.
///
/// # Parameters
/// * `blueprint` - The service blueprint.
/// * `blueprint_id` - The ID of the service blueprint.
/// * `service_id` - The ID of the service instance.
///
/// # Returns
/// * `Result<(bool, u64), DispatchErrorWithPostInfo>` - A tuple containing:
/// - A boolean indicating if the default value should be used
/// - The heartbeat interval in blocks
pub fn get_heartbeat_interval_hook(
blueprint: &ServiceBlueprint<T::Constraints>,
blueprint_id: u64,
service_id: u64,
) -> Result<(bool, u32), DispatchErrorWithPostInfo> {
#[allow(deprecated)]
let query = Function {
name: String::from("getHeartbeatInterval"),
inputs: vec![
ethabi::Param {
name: String::from("blueprintId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
ethabi::Param {
name: String::from("serviceId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
],
outputs: Default::default(),
constant: None,
state_mutability: StateMutability::View,
};
let mbsm = Self::mbsm_address_of(blueprint)?;
let (info, _weight) = Self::dispatch_evm_call(
mbsm,
query.clone(),
&[
Token::Uint(ethabi::Uint::from(blueprint_id)),
Token::Uint(ethabi::Uint::from(service_id)),
],
Zero::zero(),
)?;

// Decode the result and return it, falling back on the default value if it fails
let maybe_value = info.exit_reason.is_succeed().then_some(&info.value);
if let Some(data) = maybe_value {
let result = query.decode_output(data).map_err(|_| Error::<T>::EVMAbiDecode)?;
let use_default = result.first().ok_or(Error::<T>::EVMAbiDecode)?;
let interval = result.get(1).ok_or(Error::<T>::EVMAbiDecode)?;
if let ethabi::Token::Bool(use_default) = use_default {
if let ethabi::Token::Uint(interval) = interval {
Ok((*use_default, interval.as_u32()))
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
}

/// Gets the heartbeat threshold for a service instance by calling the blueprint's EVM contract.
///
/// This function dispatches a call to the `getHeartbeatThreshold` function of the service
/// blueprint's manager contract to retrieve the heartbeat threshold percentage for a service.
///
/// # Parameters
/// * `blueprint` - The service blueprint.
/// * `blueprint_id` - The ID of the service blueprint.
/// * `service_id` - The ID of the service instance.
///
/// # Returns
/// * `Result<(bool, u64), DispatchErrorWithPostInfo>` - A tuple containing:
/// - A boolean indicating if the default value should be used
/// - The heartbeat threshold percentage
pub fn get_heartbeat_threshold_hook(
blueprint: &ServiceBlueprint<T::Constraints>,
blueprint_id: u64,
service_id: u64,
) -> Result<(bool, u8), DispatchErrorWithPostInfo> {
#[allow(deprecated)]
let query = Function {
name: String::from("getHeartbeatThreshold"),
inputs: vec![
ethabi::Param {
name: String::from("blueprintId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
ethabi::Param {
name: String::from("serviceId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
],
outputs: Default::default(),
constant: None,
state_mutability: StateMutability::View,
};
let mbsm = Self::mbsm_address_of(blueprint)?;
let (info, _weight) = Self::dispatch_evm_call(
mbsm,
query.clone(),
&[
Token::Uint(ethabi::Uint::from(blueprint_id)),
Token::Uint(ethabi::Uint::from(service_id)),
],
Zero::zero(),
)?;

// Decode the result and return it, falling back on the default value if it fails
let maybe_value = info.exit_reason.is_succeed().then_some(&info.value);
if let Some(data) = maybe_value {
let result = query.decode_output(data).map_err(|_| Error::<T>::EVMAbiDecode)?;
let use_default = result.first().ok_or(Error::<T>::EVMAbiDecode)?;
let threshold = result.get(1).ok_or(Error::<T>::EVMAbiDecode)?;
if let ethabi::Token::Bool(use_default) = use_default {
if let ethabi::Token::Uint(threshold) = threshold {
Ok((
*use_default,
threshold.as_u64().try_into().map_err(|_| Error::<T>::EVMAbiDecode)?,
))
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
}

/// Gets the slashing window for a service instance by calling the blueprint's EVM contract.
///
/// This function dispatches a call to the `getSlashingWindow` function of the service
/// blueprint's manager contract to retrieve the slashing window in blocks for a service.
///
/// # Parameters
/// * `blueprint` - The service blueprint.
/// * `blueprint_id` - The ID of the service blueprint.
/// * `service_id` - The ID of the service instance.
///
/// # Returns
/// * `Result<(bool, u64), DispatchErrorWithPostInfo>` - A tuple containing:
/// - A boolean indicating if the default value should be used
/// - The slashing window in blocks
pub fn get_slashing_window_hook(
blueprint: &ServiceBlueprint<T::Constraints>,
blueprint_id: u64,
service_id: u64,
) -> Result<(bool, u32), DispatchErrorWithPostInfo> {
#[allow(deprecated)]
let query = Function {
name: String::from("getSlashingWindow"),
inputs: vec![
ethabi::Param {
name: String::from("blueprintId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
ethabi::Param {
name: String::from("serviceId"),
kind: ethabi::ParamType::Uint(64),
internal_type: None,
},
],
outputs: Default::default(),
constant: None,
state_mutability: StateMutability::View,
};
let mbsm = Self::mbsm_address_of(blueprint)?;
let (info, _weight) = Self::dispatch_evm_call(
mbsm,
query.clone(),
&[
Token::Uint(ethabi::Uint::from(blueprint_id)),
Token::Uint(ethabi::Uint::from(service_id)),
],
Zero::zero(),
)?;

// Decode the result and return it, falling back on the default value if it fails
let maybe_value = info.exit_reason.is_succeed().then_some(&info.value);
if let Some(data) = maybe_value {
let result = query.decode_output(data).map_err(|_| Error::<T>::EVMAbiDecode)?;
let use_default = result.first().ok_or(Error::<T>::EVMAbiDecode)?;
let window = result.get(1).ok_or(Error::<T>::EVMAbiDecode)?;
if let ethabi::Token::Bool(use_default) = use_default {
if let ethabi::Token::Uint(window) = window {
Ok((*use_default, window.as_u32()))
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
} else {
Ok((true, 0))
}
}

/// Hook to be called upon new service request.
///
/// This function is called when a service request is made. It performs an EVM call
Expand Down Expand Up @@ -1435,7 +1645,7 @@ impl<T: Config> Pallet<T> {
/// * `blueprint_id` - The ID of the blueprint
/// * `service_id` - The ID of the service
/// * `operator` - The account ID of the operator being slashed
/// * `slash_percent` - The percentage being slashed (0-100)
/// * `amount` - The amount being slashed
///
/// # Returns
/// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing a boolean
Expand Down
1 change: 1 addition & 0 deletions pallets/services/src/functions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod approve;
pub mod evm_hooks;
pub mod membership;
pub mod qos;
pub mod register;
pub mod reject;
pub mod request;
Loading
Loading