From 8138c78fd9cb4e2c1a878616de963db6c327fc24 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Tue, 06 Sep 2022 07:15:58 +0000 Subject: [PATCH] Merge branch 'feature/app-staking' of https://github.com/UniqueNetwork/unique-chain into feature/app-staking --- --- a/Cargo.lock +++ b/Cargo.lock @@ -5735,6 +5735,7 @@ name = "pallet-evm-contract-helpers" version = "0.2.0" dependencies = [ + "ethereum", "evm-coder", "fp-evm-mapping", "frame-support", --- a/pallets/evm-contract-helpers/Cargo.toml +++ b/pallets/evm-contract-helpers/Cargo.toml @@ -9,6 +9,7 @@ "derive", ] } log = { default-features = false, version = "0.4.14" } +ethereum = { version = "0.12.0", default-features = false } # Substrate frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" } --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -17,7 +17,9 @@ //! Implementation of magic contract use core::marker::PhantomData; -use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*}; +use evm_coder::{ + abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog, +}; use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm}; use pallet_evm::{ ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle, @@ -33,6 +35,35 @@ use up_sponsorship::SponsorshipHandler; use sp_std::vec::Vec; +/// Pallet events. +#[derive(ToLog)] +pub enum ContractHelpersEvents { + /// Contract sponsor was set. + ContractSponsorSet { + /// Contract address of the affected collection. + #[indexed] + contract_address: address, + /// New sponsor address. + sponsor: address, + }, + + /// New sponsor was confirm. + ContractSponsorshipConfirmed { + /// Contract address of the affected collection. + #[indexed] + contract_address: address, + /// New sponsor address. + sponsor: address, + }, + + /// Collection sponsor was removed. + ContractSponsorRemoved { + /// Contract address of the affected collection. + #[indexed] + contract_address: address, + }, +} + /// See [`ContractHelpersCall`] pub struct ContractHelpers(SubstrateRecorder); impl WithRecorder for ContractHelpers { @@ -46,7 +77,7 @@ } /// @title Magic contract, which allows users to reconfigure other contracts -#[solidity_interface(name = ContractHelpers)] +#[solidity_interface(name = ContractHelpers, events(ContractHelpersEvents))] impl ContractHelpers where T::AccountId: AsRef<[u8; 32]>, @@ -91,8 +122,12 @@ self.recorder().consume_sload()?; self.recorder().consume_sstore()?; - Pallet::::self_sponsored_enable(&T::CrossAccountId::from_eth(caller), contract_address) - .map_err(dispatch_to_evm::)?; + Pallet::::force_set_sponsor( + &T::CrossAccountId::from_eth(caller), + contract_address, + &T::CrossAccountId::from_eth(contract_address), + ) + .map_err(dispatch_to_evm::)?; Ok(()) } --- a/pallets/evm-contract-helpers/src/lib.rs +++ b/pallets/evm-contract-helpers/src/lib.rs @@ -16,7 +16,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -#![deny(missing_docs)] +#![warn(missing_docs)] use codec::{Decode, Encode, MaxEncodedLen}; pub use pallet::*; @@ -27,18 +27,24 @@ #[frame_support::pallet] pub mod pallet { pub use super::*; + use crate::eth::ContractHelpersEvents; use frame_support::pallet_prelude::*; use pallet_evm_coder_substrate::DispatchResult; use sp_core::H160; - use pallet_evm::account::CrossAccountId; + use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use up_data_structs::SponsorshipState; + use evm_coder::ToLog; #[pallet::config] pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config { + /// Overarching event type. + type Event: IsType<::Event> + From>; + /// Address, under which magic contract will be available type ContractAddress: Get; + /// In case of enabled sponsoring, but no sponsoring rate limit set, /// this value will be used implicitly type DefaultSponsoringRateLimit: Get; @@ -150,6 +156,32 @@ QueryKind = ValueQuery, >; + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// Contract sponsor was set. + ContractSponsorSet( + /// Contract address of the affected collection. + H160, + /// New sponsor address. + T::AccountId, + ), + + /// New sponsor was confirm. + ContractSponsorshipConfirmed( + /// Contract address of the affected collection. + H160, + /// New sponsor address. + T::AccountId, + ), + + /// Collection sponsor was removed. + ContractSponsorRemoved( + /// Contract address of the affected collection. + H160, + ), + } + impl Pallet { /// Get contract owner. pub fn contract_owner(contract: H160) -> H160 { @@ -169,43 +201,108 @@ contract, SponsorshipState::::Unconfirmed(sponsor.clone()), ); + + >::deposit_event(Event::::ContractSponsorSet( + contract, + sponsor.as_sub().clone(), + )); + >::deposit_log( + ContractHelpersEvents::ContractSponsorSet { + contract_address: contract, + sponsor: *sponsor.as_eth(), + } + .to_log(contract), + ); Ok(()) } - /// Set `contract` as self sponsored. + /// Set sponsor as already confirmed. /// /// `sender` must be owner of contract. - pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult { - Pallet::::ensure_owner(contract, *sender.as_eth())?; + pub fn force_set_sponsor( + sender: &T::CrossAccountId, + contract_address: H160, + sponsor: &T::CrossAccountId, + ) -> DispatchResult { + Pallet::::ensure_owner(contract_address, *sender.as_eth())?; Sponsoring::::insert( - contract, + contract_address, SponsorshipState::::Confirmed(T::CrossAccountId::from_eth( - contract, + contract_address, )), ); + + let eth_sponsor = *sponsor.as_eth(); + let sub_sponsor = sponsor.as_sub().clone(); + + >::deposit_event(Event::::ContractSponsorSet( + contract_address, + sub_sponsor.clone(), + )); + >::deposit_log( + ContractHelpersEvents::ContractSponsorSet { + contract_address, + sponsor: eth_sponsor, + } + .to_log(contract_address), + ); + + >::deposit_event(Event::::ContractSponsorshipConfirmed( + contract_address, + sub_sponsor, + )); + >::deposit_log( + ContractHelpersEvents::ContractSponsorshipConfirmed { + contract_address, + sponsor: eth_sponsor, + } + .to_log(contract_address), + ); + Ok(()) } /// Remove sponsor for `contract`. /// /// `sender` must be owner of contract. - pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult { - Pallet::::ensure_owner(contract, *sender.as_eth())?; - Sponsoring::::remove(contract); + pub fn remove_sponsor(sender: &T::CrossAccountId, contract_address: H160) -> DispatchResult { + Pallet::::ensure_owner(contract_address, *sender.as_eth())?; + Sponsoring::::remove(contract_address); + + >::deposit_event(Event::::ContractSponsorRemoved(contract_address)); + >::deposit_log( + ContractHelpersEvents::ContractSponsorRemoved { contract_address }.to_log(contract_address), + ); + Ok(()) } /// Confirm sponsorship. /// /// `sender` must be same that set via [`set_sponsor`]. - pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult { - match Sponsoring::::get(contract) { + pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract_address: H160) -> DispatchResult { + match Sponsoring::::get(contract_address) { SponsorshipState::Unconfirmed(sponsor) => { ensure!(sponsor == *sender, Error::::NoPermission); + let eth_sponsor = *sponsor.as_eth(); + let sub_sponsor = sponsor.as_sub().clone(); Sponsoring::::insert( - contract, + contract_address, SponsorshipState::::Confirmed(sponsor), ); + + >::deposit_event(Event::::ContractSponsorshipConfirmed( + contract_address, + sub_sponsor, + )); + >::deposit_log( + ContractHelpersEvents::ContractSponsorshipConfirmed { + contract_address, + sponsor: eth_sponsor, + } + .to_log(contract_address), + ); + Ok(()) } SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => { --- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol +++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol @@ -21,9 +21,19 @@ } } +/// @dev inlined interface +contract ContractHelpersEvents { + event ContractSponsorSet(address indexed contractAddress, address sponsor); + event ContractSponsorshipConfirmed( + address indexed contractAddress, + address sponsor + ); + event ContractSponsorRemoved(address indexed contractAddress); +} + /// @title Magic contract, which allows users to reconfigure other contracts /// @dev the ERC-165 identifier for this interface is 0xd77fab70 -contract ContractHelpers is Dummy, ERC165 { +contract ContractHelpers is Dummy, ERC165, ContractHelpersEvents { /// Get user, which deployed specified contract /// @dev May return zero address in case if contract is deployed /// using uniquenetwork evm-migration pallet, or using other terms not --- a/runtime/common/config/ethereum.rs +++ b/runtime/common/config/ethereum.rs @@ -112,6 +112,7 @@ } impl pallet_evm_contract_helpers::Config for Runtime { + type Event = Event; type ContractAddress = HelpersContractAddress; type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; } --- a/runtime/common/construct_runtime/mod.rs +++ b/runtime/common/construct_runtime/mod.rs @@ -85,7 +85,7 @@ Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101, EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, - EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151, + EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage, Event} = 151, EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, } --- a/tests/src/eth/api/ContractHelpers.sol +++ b/tests/src/eth/api/ContractHelpers.sol @@ -12,9 +12,19 @@ function supportsInterface(bytes4 interfaceID) external view returns (bool); } +/// @dev inlined interface +interface ContractHelpersEvents { + event ContractSponsorSet(address indexed contractAddress, address sponsor); + event ContractSponsorshipConfirmed( + address indexed contractAddress, + address sponsor + ); + event ContractSponsorRemoved(address indexed contractAddress); +} + /// @title Magic contract, which allows users to reconfigure other contracts /// @dev the ERC-165 identifier for this interface is 0xd77fab70 -interface ContractHelpers is Dummy, ERC165 { +interface ContractHelpers is Dummy, ERC165, ContractHelpersEvents { /// Get user, which deployed specified contract /// @dev May return zero address in case if contract is deployed /// using uniquenetwork evm-migration pallet, or using other terms not --- a/tests/src/eth/contractSponsoring.test.ts +++ b/tests/src/eth/contractSponsoring.test.ts @@ -24,6 +24,7 @@ SponsoringMode, createEthAccount, ethBalanceViaSub, + normalizeEvents, } from './util/helpers'; describe('Sponsoring EVM contracts', () => { @@ -36,6 +37,33 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); + itWeb3.only('Set self sponsored events', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + + const result = await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); + const events = normalizeEvents(result.events); + expect(events).to.be.deep.equal([ + { + address: flipper.options.address, + event: 'ContractSponsorSet', + args: { + contractAddress: flipper.options.address, + sponsor: flipper.options.address, + }, + }, + { + address: flipper.options.address, + event: 'ContractSponsorshipConfirmed', + args: { + contractAddress: flipper.options.address, + sponsor: flipper.options.address, + }, + }, + ]); + }); + itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); @@ -75,6 +103,26 @@ expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true; }); + itWeb3('Set sponsor event', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + + const result = await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); + const events = normalizeEvents(result.events); + expect(events).to.be.deep.equal([ + { + address: flipper.options.address, + event: 'ContractSponsorSet', + args: { + contractAddress: flipper.options.address, + sponsor: sponsor, + }, + }, + ]); + }); + itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); @@ -97,6 +145,26 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); + itWeb3('Confirm sponsorship event', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; + const result = await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); + const events = normalizeEvents(result.events); + expect(events).to.be.deep.equal([ + { + address: flipper.options.address, + event: 'ContractSponsorshipConfirmed', + args: { + contractAddress: flipper.options.address, + sponsor: sponsor, + }, + }, + ]); + }); + itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); @@ -160,6 +228,28 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; }); + itWeb3('Remove sponsor event', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, owner); + const helpers = contractHelpers(web3, owner); + + await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); + await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); + + const result = await helpers.methods.removeSponsor(flipper.options.address).send(); + const events = normalizeEvents(result.events); + expect(events).to.be.deep.equal([ + { + address: flipper.options.address, + event: 'ContractSponsorRemoved', + args: { + contractAddress: flipper.options.address, + }, + }, + ]); + }); + itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); --- a/tests/src/eth/util/contractHelpersAbi.json +++ b/tests/src/eth/util/contractHelpersAbi.json @@ -1,5 +1,56 @@ [ { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], + "name": "ContractSponsorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "sponsor", + "type": "address" + } + ], + "name": "ContractSponsorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "sponsor", + "type": "address" + } + ], + "name": "ContractSponsorshipConfirmed", + "type": "event" + }, + { "inputs": [ { "internalType": "address", -- gitstuff