From b230e7e5f037551fc256514d36e9a62605a42adb Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 23 Nov 2022 17:26:33 +0000 Subject: [PATCH] feat: fake event injection --- --- a/Cargo.lock +++ b/Cargo.lock @@ -6038,6 +6038,7 @@ name = "pallet-evm-migration" version = "0.1.1" dependencies = [ + "ethereum", "fp-evm", "frame-benchmarking", "frame-support", --- a/pallets/evm-migration/Cargo.toml +++ b/pallets/evm-migration/Cargo.toml @@ -8,6 +8,7 @@ scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } +ethereum = { version = "0.12.0", default-features = false } frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } --- a/pallets/evm-migration/src/benchmarking.rs +++ b/pallets/evm-migration/src/benchmarking.rs @@ -20,9 +20,11 @@ use frame_benchmarking::benchmarks; use frame_system::RawOrigin; use sp_core::{H160, H256}; -use sp_std::vec::Vec; +use sp_std::{vec::Vec, vec}; benchmarks! { + where_clause { where ::RuntimeEvent: codec::Encode } + begin { }: _(RawOrigin::Root, H160::default()) @@ -45,4 +47,19 @@ let data: Vec = (0..b as u8).collect(); >::begin(RawOrigin::Root.into(), address)?; }: _(RawOrigin::Root, address, data) + + insert_eth_logs { + let b in 0..200; + let logs = (0..b).map(|_| ethereum::Log { + address: H160([b as u8; 20]), + data: vec![b as u8; 128], + topics: vec![H256([b as u8; 32]); 6], + }).collect::>(); + }: _(RawOrigin::Root, logs) + + insert_events { + let b in 0..200; + use codec::Encode; + let logs = (0..b).map(|_| ::RuntimeEvent::from(crate::Event::::TestEvent).encode()).collect::>(); + }: _(RawOrigin::Root, logs) } --- a/pallets/evm-migration/src/lib.rs +++ b/pallets/evm-migration/src/lib.rs @@ -25,8 +25,11 @@ #[frame_support::pallet] pub mod pallet { - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + use frame_support::{ + pallet_prelude::{*, DispatchResult}, + traits::IsType, + }; + use frame_system::pallet_prelude::{*, OriginFor}; use sp_core::{H160, H256}; use sp_std::vec::Vec; use super::weights::WeightInfo; @@ -36,6 +39,8 @@ pub trait Config: frame_system::Config + pallet_evm::Config { /// Weights type WeightInfo: WeightInfo; + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } type SelfWeightOf = ::WeightInfo; @@ -44,12 +49,20 @@ #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); + #[pallet::event] + pub enum Event { + /// This event is used in benchmarking and can be used for tests + TestEvent, + } + #[pallet::error] pub enum Error { /// Can only migrate to empty address. AccountNotEmpty, /// Migration of this account is not yet started, or already finished. AccountIsNotMigrating, + /// Failed to decode event bytes + BadEvent, } #[pallet::storage] @@ -107,6 +120,30 @@ >::remove(address); Ok(()) } + + /// Create ethereum events attached to the fake transaction + #[pallet::weight(>::insert_eth_logs(logs.len() as u32))] + pub fn insert_eth_logs(origin: OriginFor, logs: Vec) -> DispatchResult { + ensure_root(origin)?; + for log in logs { + >::deposit_log(log); + } + // Transactions is created by FakeTransactionFinalizer + Ok(()) + } + + /// Create substrate events + #[pallet::weight(>::insert_events(events.len() as u32))] + pub fn insert_events(origin: OriginFor, events: Vec>) -> DispatchResult { + ensure_root(origin)?; + for event in events { + >::deposit_event( + ::RuntimeEvent::decode(&mut event.as_slice()) + .map_err(|_| >::BadEvent)?, + ); + } + Ok(()) + } } /// Implements [`pallet_evm::OnMethodCall`], which reserves accounts with pending migration --- a/pallets/evm-migration/src/weights.rs +++ b/pallets/evm-migration/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_evm_migration //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-23, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -37,6 +37,8 @@ fn begin() -> Weight; fn set_data(b: u32, ) -> Weight; fn finish(b: u32, ) -> Weight; + fn insert_eth_logs(b: u32, ) -> Weight; + fn insert_events(b: u32, ) -> Weight; } /// Weights for pallet_evm_migration using the Substrate node and recommended hardware. @@ -46,26 +48,38 @@ // Storage: System Account (r:1 w:0) // Storage: EVM AccountCodes (r:1 w:0) fn begin() -> Weight { - Weight::from_ref_time(8_035_000) + Weight::from_ref_time(16_080_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: EvmMigration MigrationPending (r:1 w:0) // Storage: EVM AccountStorages (r:0 w:1) fn set_data(b: u32, ) -> Weight { - Weight::from_ref_time(3_076_000) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(828_000).saturating_mul(b as u64)) + Weight::from_ref_time(7_945_000 as u64) + // Standard Error: 1_272 + .saturating_add(Weight::from_ref_time(1_056_832 as u64).saturating_mul(b as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(b as u64))) } // Storage: EvmMigration MigrationPending (r:1 w:1) // Storage: EVM AccountCodes (r:0 w:1) - fn finish(_b: u32, ) -> Weight { - Weight::from_ref_time(6_591_000) + fn finish(b: u32, ) -> Weight { + Weight::from_ref_time(8_336_000 as u64) + // Standard Error: 89 + .saturating_add(Weight::from_ref_time(6_411 as u64).saturating_mul(b as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + fn insert_eth_logs(b: u32, ) -> Weight { + Weight::from_ref_time(3_447_000 as u64) + // Standard Error: 843 + .saturating_add(Weight::from_ref_time(901_039 as u64).saturating_mul(b as u64)) + } + fn insert_events(b: u32, ) -> Weight { + Weight::from_ref_time(3_457_000 as u64) + // Standard Error: 1_460 + .saturating_add(Weight::from_ref_time(1_491_611 as u64).saturating_mul(b as u64)) + } } // For backwards compatibility and tests @@ -74,24 +88,36 @@ // Storage: System Account (r:1 w:0) // Storage: EVM AccountCodes (r:1 w:0) fn begin() -> Weight { - Weight::from_ref_time(8_035_000) + Weight::from_ref_time(16_080_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: EvmMigration MigrationPending (r:1 w:0) // Storage: EVM AccountStorages (r:0 w:1) fn set_data(b: u32, ) -> Weight { - Weight::from_ref_time(3_076_000) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(828_000).saturating_mul(b as u64)) + Weight::from_ref_time(7_945_000 as u64) + // Standard Error: 1_272 + .saturating_add(Weight::from_ref_time(1_056_832 as u64).saturating_mul(b as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(b as u64))) } // Storage: EvmMigration MigrationPending (r:1 w:1) // Storage: EVM AccountCodes (r:0 w:1) - fn finish(_b: u32, ) -> Weight { - Weight::from_ref_time(6_591_000) + fn finish(b: u32, ) -> Weight { + Weight::from_ref_time(8_336_000 as u64) + // Standard Error: 89 + .saturating_add(Weight::from_ref_time(6_411 as u64).saturating_mul(b as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } + fn insert_eth_logs(b: u32, ) -> Weight { + Weight::from_ref_time(3_447_000 as u64) + // Standard Error: 843 + .saturating_add(Weight::from_ref_time(901_039 as u64).saturating_mul(b as u64)) + } + fn insert_events(b: u32, ) -> Weight { + Weight::from_ref_time(3_457_000 as u64) + // Standard Error: 1_460 + .saturating_add(Weight::from_ref_time(1_491_611 as u64).saturating_mul(b as u64)) + } } --- a/runtime/common/config/ethereum.rs +++ b/runtime/common/config/ethereum.rs @@ -91,6 +91,7 @@ } impl pallet_evm_migration::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_evm_migration::weights::SubstrateWeight; } --- a/runtime/common/construct_runtime/mod.rs +++ b/runtime/common/construct_runtime/mod.rs @@ -92,7 +92,7 @@ EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage, Event} = 151, EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, - EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, + EvmMigration: pallet_evm_migration::{Pallet, Call, Storage, Event} = 153, Maintenance: pallet_maintenance::{Pallet, Call, Storage, Event} = 154, --- a/tests/src/eth/migration.seqtest.ts +++ b/tests/src/eth/migration.seqtest.ts @@ -16,16 +16,36 @@ import {expect, itEth, usingEthPlaygrounds} from './util'; import {IKeyringPair} from '@polkadot/types/types'; +import {Struct} from '@polkadot/types'; + +import {IEvent} from '../util/playgrounds/types'; +import {ApiPromise} from '@polkadot/api'; + +const encodeEvent = (api: ApiPromise, pallet: string, palletEvents: string, event: string, fields: any) => { + const palletIndex = api.runtimeMetadata.asV14.pallets.find(p => p.name.toString() == pallet)!.index.toNumber(); + const eventMeta = api.events[palletEvents][event].meta; + const eventIndex = eventMeta.index.toNumber(); + const data = [ + palletIndex, eventIndex, + ]; + const metaEvent = api.registry.findMetaEvent(new Uint8Array(data)); + data.push(...new Struct(api.registry, {data: metaEvent}, {data: fields}).toU8a()); + const typeName = api.registry.lookup.names.find(n => n.endsWith('RuntimeEvent'))!; + return api.registry.createType(typeName, new Uint8Array(data)).toHex(); +}; + describe('EVM Migrations', () => { let superuser: IKeyringPair; + let charlie: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (_helper, privateKey) => { superuser = await privateKey('//Alice'); + charlie = await privateKey('//Charlie'); }); }); - + // todo:playgrounds requires sudo, look into later itEth('Deploy contract saved state', async ({helper}) => { /* @@ -105,4 +125,83 @@ expect(await contract.methods.get(i).call()).to.be.equal(i.toString()); } }); + itEth('Fake collection creation on substrate side', async ({helper}) => { + const txInsertEvents = helper.constructApiCall('api.tx.evmMigration.insertEvents', [[ + encodeEvent(helper.api!, 'Common', 'common', 'CollectionCreated', [ + // Collection Id + 9999, + // Collection mode: NFT + 1, + // Owner + charlie.address, + ]), + ]]); + await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txInsertEvents]); + const event = helper.chainLog[helper.chainLog.length - 1].events as IEvent[]; + const eventStrings = event.map(e => `${e.section}.${e.method}`); + + expect(eventStrings).to.contain('common.CollectionCreated'); + }); + itEth('Fake token creation on substrate side', async ({helper}) => { + const txInsertEvents = helper.constructApiCall('api.tx.evmMigration.insertEvents', [[ + encodeEvent(helper.api!, 'Common', 'common', 'ItemCreated', [ + // Collection Id + 9999, + // TokenId + 9999, + // Owner + {Substrate: charlie.address}, + // Amount + 1, + ]), + ]]); + await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txInsertEvents]); + const event = helper.chainLog[helper.chainLog.length - 1].events as IEvent[]; + const eventStrings = event.map(e => `${e.section}.${e.method}`); + + expect(eventStrings).to.contain('common.ItemCreated'); + }); + itEth('Fake token creation on ethereum side', async ({helper}) => { + const collection = await helper.nft.mintCollection(superuser); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const caller = await helper.eth.createAccountWithBalance(superuser); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller); + + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); + + { + const txInsertEthLogs = helper.constructApiCall('api.tx.evmMigration.insertEthLogs', [[ + { + // Contract, which has emitted this log + address: collectionAddress, + + topics: [ + // First topic - event signature + helper.getWeb3().eth.abi.encodeEventSignature('Transfer(address,address,uint256)'), + // Rest of topics - indexed event fields in definition order + helper.getWeb3().eth.abi.encodeParameter('address', '0x' + '00'.repeat(20)), + helper.getWeb3().eth.abi.encodeParameter('address', caller), + helper.getWeb3().eth.abi.encodeParameter('uint256', 9999), + ], + + // Every field coming from event, which is not marked as indexed, should be encoded here + // NFT transfer has no such fields, but here is an example for some other possible event: + // data: helper.getWeb3().eth.abi.encodeParameters(['uint256', 'address'], [22, collectionAddress]) + data: [], + }, + ]]); + await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txInsertEthLogs]); + } + + if (events.length == 0) await helper.wait.newBlocks(1); + const event = events[0]; + + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0x' + '00'.repeat(20)); + expect(event.returnValues.to).to.be.equal(caller); + expect(event.returnValues.tokenId).to.be.equal('9999'); + }); }); -- gitstuff