Skip to content

Commit

Permalink
feat: using ERC20 and Assets works on Both EVM and Substrate
Browse files Browse the repository at this point in the history
  • Loading branch information
shekohex committed Dec 6, 2024
1 parent 20b533c commit 402da1c
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 14 deletions.
9 changes: 4 additions & 5 deletions pallets/services/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,12 +902,12 @@ pub mod module {
};
}

let service_id = Self::next_instance_id();
let request_id = NextServiceRequestId::<T>::get();
let (allowed, _weight) = Self::on_request_hook(
&blueprint,
blueprint_id,
&caller,
service_id,
request_id,
&preferences,
&request_args,
&permitted_callers,
Expand All @@ -918,14 +918,11 @@ pub mod module {
native_value,
)?;

ensure!(allowed, Error::<T>::InvalidRequestInput);

let permitted_callers =
BoundedVec::<_, MaxPermittedCallersOf<T>>::try_from(permitted_callers)
.map_err(|_| Error::<T>::MaxPermittedCallersExceeded)?;
let assets = BoundedVec::<_, MaxAssetsPerServiceOf<T>>::try_from(assets)
.map_err(|_| Error::<T>::MaxAssetsPerServiceExceeded)?;
let request_id = NextServiceRequestId::<T>::get();
let operators = pending_approvals
.iter()
.cloned()
Expand All @@ -948,6 +945,8 @@ pub mod module {
permitted_callers,
operators_with_approval_state,
};

ensure!(allowed, Error::<T>::InvalidRequestInput);
ServiceRequests::<T>::insert(request_id, service_request);
NextServiceRequestId::<T>::set(request_id.saturating_add(1));

Expand Down
10 changes: 5 additions & 5 deletions pallets/services/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use frame_election_provider_support::{
onchain, SequentialPhragmen,
};
use frame_support::{
assert_ok, construct_runtime, parameter_types,
construct_runtime, parameter_types,
traits::{ConstU128, ConstU32, OneSessionHandler},
};
use frame_support::{derive_impl, traits::AsEnsureOriginWithArg};
Expand Down Expand Up @@ -643,12 +643,12 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AccountId>) -> sp_io::TestE
])
.unwrap(),
Default::default(),
30_000,
300_000,
true,
false,
);

assert_ok!(call);
assert_eq!(call.map(|info| info.exit_reason.is_succeed()).ok(), Some(true));
// Mint
for i in 1..=authorities.len() {
let call = <Runtime as pallet_services::Config>::EvmRunner::call(
Expand Down Expand Up @@ -678,12 +678,12 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AccountId>) -> sp_io::TestE
])
.unwrap(),
Default::default(),
30_000,
300_000,
true,
false,
);

assert_ok!(call);
assert_eq!(call.map(|info| info.exit_reason.is_succeed()).ok(), Some(true));
}
});

Expand Down
54 changes: 54 additions & 0 deletions pallets/services/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,60 @@ fn request_service_with_payment_asset() {
});
}

#[test]
fn request_service_with_payment_token() {
new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| {
System::set_block_number(1);
assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM));
let alice = mock_pub_key(ALICE);
let blueprint = cggmp21_blueprint();

assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint));
let bob = mock_pub_key(BOB);
assert_ok!(Services::register(
RuntimeOrigin::signed(bob.clone()),
0,
OperatorPreferences { key: zero_key(), price_targets: Default::default() },
Default::default(),
0,
));

let charlie = mock_pub_key(CHARLIE);
assert_ok!(Services::request(
RuntimeOrigin::signed(charlie.clone()),
0,
vec![],
vec![bob.clone()],
Default::default(),
vec![TNT, USDC, WETH],
100,
Asset::Erc20(USDC_ERC20),
5 * 10u128.pow(6), // 5 USDC
));

assert_eq!(ServiceRequests::<Runtime>::iter_keys().collect::<Vec<_>>().len(), 1);

// The Pallet address now has 5 USDC
assert_ok!(
Services::query_erc20_balance_of(USDC_ERC20, Services::address()).map(|(b, _)| b),
U256::from(5 * 10u128.pow(6))
);

// Bob approves the request
assert_ok!(Services::approve(
RuntimeOrigin::signed(bob.clone()),
0,
Percent::from_percent(10)
));

// The request is now fully approved
assert_eq!(ServiceRequests::<Runtime>::iter_keys().collect::<Vec<_>>().len(), 0);

// Now the service should be initiated
assert!(Instances::<Runtime>::contains_key(0));
});
}

#[test]
fn job_calls() {
new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| {
Expand Down
2 changes: 1 addition & 1 deletion precompiles/services/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ where

/// Request a new service.
#[precompile::public(
"requestService(uint256,uint256[],bytes,bytes,bytes,uint256,address,uint256)"
"requestService(uint256,uint256[],bytes,bytes,bytes,uint256,uint256,address,uint256)"
)]
fn request_service(
handle: &mut impl PrecompileHandle,
Expand Down
145 changes: 144 additions & 1 deletion precompiles/services/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.
#![allow(clippy::all)]
use super::*;
use core::ops::Mul;
use ethabi::Uint;
use frame_election_provider_support::{
bounds::{ElectionBounds, ElectionBoundsBuilder},
onchain, SequentialPhragmen,
Expand All @@ -29,12 +31,14 @@ use frame_support::{derive_impl, traits::AsEnsureOriginWithArg};
use frame_system::EnsureRoot;
use mock_evm::MockedEvmRunner;
use pallet_evm::GasWeightMapping;
use pallet_services::traits::EvmRunner;
use pallet_services::{EvmAddressMapping, EvmGasWeightMapping};
use pallet_session::historical as pallet_session_historical;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
use sp_core::{self, sr25519, sr25519::Public as sr25519Public, ConstU32, RuntimeDebug, H160};
use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr};
use sp_runtime::{
Expand Down Expand Up @@ -358,6 +362,21 @@ impl From<TestAccount> for AccountId32 {
}
}

impl From<AccountId32> for TestAccount {
fn from(x: AccountId32) -> Self {
let bytes: [u8; 32] = x.into();
match bytes {
a if a == [1u8; 32] => TestAccount::Alex,
a if a == [2u8; 32] => TestAccount::Bob,
a if a == [3u8; 32] => TestAccount::Charlie,
a if a == [4u8; 32] => TestAccount::Dave,
a if a == [5u8; 32] => TestAccount::Eve,
a if a == PRECOMPILE_ADDRESS_BYTES => TestAccount::PrecompileAddress,
_ => TestAccount::Empty,
}
}
}

impl From<TestAccount> for sp_core::sr25519::Public {
fn from(x: TestAccount) -> Self {
match x {
Expand Down Expand Up @@ -577,6 +596,7 @@ pub fn mock_authorities(vec: Vec<u8>) -> Vec<AccountId> {

pub const MBSM: H160 = H160([0x12; 20]);
pub const CGGMP21_BLUEPRINT: H160 = H160([0x21; 20]);
pub const USDC_ERC20: H160 = H160([0x23; 20]);

pub const TNT: AssetId = 0;
pub const USDC: AssetId = 1;
Expand All @@ -600,7 +620,7 @@ impl ExtBuilder {
pub fn new_test_ext_raw_authorities(authorities: Vec<AccountId>) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
// We use default for brevity, but you can configure as desired if needed.
let balances: Vec<_> = authorities.iter().map(|i| (i.clone(), 20_000_u128)).collect();
let balances: Vec<_> = authorities.iter().map(|i| (i.clone(), 20_000_000_u128)).collect();
pallet_balances::GenesisConfig::<Runtime> { balances }
.assimilate_storage(&mut t)
.unwrap();
Expand Down Expand Up @@ -661,18 +681,141 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AccountId>) -> sp_io::TestE
MBSM,
);

create_contract(
include_str!("../../../pallets/services/src/test-artifacts/MockERC20.hex"),
USDC_ERC20,
);

// Add some initial balance to the authorities in the EVM pallet
for a in authorities.iter().cloned() {
evm_accounts.insert(
TestAccount::from(a).into(),
fp_evm::GenesisAccount {
code: vec![],
storage: Default::default(),
nonce: Default::default(),
balance: Uint::from(1_000).mul(Uint::from(10).pow(Uint::from(18))),
},
);
}

let evm_config =
pallet_evm::GenesisConfig::<Runtime> { accounts: evm_accounts, ..Default::default() };

evm_config.assimilate_storage(&mut t).unwrap();

let assets_config = pallet_assets::GenesisConfig::<Runtime> {
assets: vec![
(USDC, authorities[0].clone(), true, 100_000), // 1 cent.
(WETH, authorities[1].clone(), true, 100), // 100 wei.
(WBTC, authorities[2].clone(), true, 100), // 100 satoshi.
],
metadata: vec![
(USDC, Vec::from(b"USD Coin"), Vec::from(b"USDC"), 6),
(WETH, Vec::from(b"Wrapped Ether"), Vec::from(b"WETH"), 18),
(WBTC, Vec::from(b"Wrapped Bitcoin"), Vec::from(b"WBTC"), 18),
],
accounts: vec![
(USDC, authorities[0].clone(), 1_000_000 * 10u128.pow(6)),
(WETH, authorities[0].clone(), 100 * 10u128.pow(18)),
(WBTC, authorities[0].clone(), 50 * 10u128.pow(18)),
//
(USDC, authorities[1].clone(), 1_000_000 * 10u128.pow(6)),
(WETH, authorities[1].clone(), 100 * 10u128.pow(18)),
(WBTC, authorities[1].clone(), 50 * 10u128.pow(18)),
//
(USDC, authorities[2].clone(), 1_000_000 * 10u128.pow(6)),
(WETH, authorities[2].clone(), 100 * 10u128.pow(18)),
(WBTC, authorities[2].clone(), 50 * 10u128.pow(18)),
],
next_asset_id: Some(4),
};

assets_config.assimilate_storage(&mut t).unwrap();

let mut ext = sp_io::TestExternalities::new(t);
ext.register_extension(KeystoreExt(Arc::new(MemoryKeystore::new()) as KeystorePtr));
ext.execute_with(|| System::set_block_number(1));
ext.execute_with(|| {
System::set_block_number(1);
Session::on_initialize(1);
<Staking as Hooks<u64>>::on_initialize(1);

let call = <Runtime as pallet_services::Config>::EvmRunner::call(
Services::address(),
USDC_ERC20,
serde_json::from_value::<ethabi::Function>(json!({
"name": "initialize",
"inputs": [
{
"name": "name_",
"type": "string",
"internalType": "string"
},
{
"name": "symbol_",
"type": "string",
"internalType": "string"
},
{
"name": "decimals_",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}))
.unwrap()
.encode_input(&[
ethabi::Token::String("USD Coin".to_string()),
ethabi::Token::String("USDC".to_string()),
ethabi::Token::Uint(6.into()),
])
.unwrap(),
Default::default(),
300_000,
true,
false,
);

assert_eq!(call.map(|info| info.exit_reason.is_succeed()).ok(), Some(true));
// Mint
for a in authorities {
let call = <Runtime as pallet_services::Config>::EvmRunner::call(
Services::address(),
USDC_ERC20,
serde_json::from_value::<ethabi::Function>(json!({
"name": "mint",
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}))
.unwrap()
.encode_input(&[
ethabi::Token::Address(TestAccount::from(a).into()),
ethabi::Token::Uint(Uint::from(100_000).mul(Uint::from(10).pow(Uint::from(6)))),
])
.unwrap(),
Default::default(),
300_000,
true,
false,
);

assert_eq!(call.map(|info| info.exit_reason.is_succeed()).ok(), Some(true));
}
});

ext
Expand Down
Loading

0 comments on commit 402da1c

Please sign in to comment.