Skip to content

Commit

Permalink
Add account migration pallet (#86)
Browse files Browse the repository at this point in the history
* Init commit

* Add todos

* Add `ValidateUnsigned`

* Add signature verify

* Add event

* Add comment

* Update message hash

* Add mock file

* Compile mock

* Add basic tests

* Add more tests

* Code clean

* Clean toml

* Format

* Install it to the runtimes

* Rename

Co-authored-by: HackFisher <[email protected]>
Co-authored-by: Xavier Lau <[email protected]>
  • Loading branch information
3 people authored Dec 6, 2022
1 parent fa47263 commit a2d5ae8
Show file tree
Hide file tree
Showing 20 changed files with 774 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions node/src/chain_spec/crab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub fn config() -> ChainSpec {
balances: Default::default(),
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
collator_selection: crab_runtime::CollatorSelectionConfig {
Expand Down Expand Up @@ -260,6 +261,7 @@ fn testnet_genesis(
},
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
collator_selection: crab_runtime::CollatorSelectionConfig {
Expand Down
2 changes: 2 additions & 0 deletions node/src/chain_spec/darwinia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ pub fn genesis_config() -> ChainSpec {
balances: Default::default(),
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
staking: StakingConfig {
Expand Down Expand Up @@ -258,6 +259,7 @@ fn testnet_genesis(
},
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
staking: StakingConfig {
Expand Down
2 changes: 2 additions & 0 deletions node/src/chain_spec/pangolin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub fn config() -> ChainSpec {
balances: Default::default(),
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
collator_selection: pangolin_runtime::CollatorSelectionConfig {
Expand Down Expand Up @@ -257,6 +258,7 @@ fn testnet_genesis(
},
transaction_payment: Default::default(),
assets: Default::default(),
account_migration: Default::default(),

// Consensus stuff.
collator_selection: pangolin_runtime::CollatorSelectionConfig {
Expand Down
60 changes: 60 additions & 0 deletions pallet/account-migration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[package]
authors = ["Darwinia Network <[email protected]>"]
description = "State storage precompiles for EVM pallet."
edition = "2021"
homepage = "https://darwinia.network"
license = "GPL-3.0"
name = "darwinia-account-migration"
readme = "README.md"
repository = "https://github.com/darwinia-network/darwinia"
version = "6.0.0"

[dependencies]
# crates.io
codec = { default-features = false, package = "parity-scale-codec", version = "3.2.1", features = ["derive"] }
scale-info = { default-features = false, version = "2.3.0", features = ["derive"] }

# darwinia
dc-primitives = { default-features = false, path = "../../core/primitives"}

# frontier
fp-ethereum = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" }
fp-evm = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" }
pallet-evm = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" }

# substrate
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" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }

[dev-dependencies]
# substrate
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }

[features]
default = ["std"]
std = [
# crates.io
"codec/std",
"scale-info/std",

# darwinia
"dc-primitives/std",

# frontier
"fp-evm/std",
"fp-ethereum/std",
"pallet-evm/std",

# paritytech
"frame-support/std",
"frame-system/std",
"sp-core/std",
"sp-runtime/std",
"sp-io/std",
"sp-std/std",
]
189 changes: 189 additions & 0 deletions pallet/account-migration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// This file is part of Darwinia.
//
// Copyright (C) 2018-2022 Darwinia Network
// SPDX-License-Identifier: GPL-3.0
//
// Darwinia is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Darwinia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Darwinia. If not, see <https://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod mock;
#[cfg(test)]
mod test;

/// Type alias for currency AccountId.
type AccountIdOf<R> = <R as frame_system::pallet::Config>::AccountId;
/// Type alias for currency balance.
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

// darwinia
use dc_primitives::Balance;
// substrate
use frame_support::traits::Currency;
#[cfg(feature = "std")]
use frame_support::traits::GenesisBuild;
use sp_core::{
crypto::ByteArray,
sr25519::{Public, Signature},
H160,
};
use sp_io::hashing::blake2_256;
use sp_runtime::{traits::Verify, AccountId32};
use sp_std::vec::Vec;

pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);

#[pallet::config]
pub trait Config: frame_system::Config + pallet_evm::Config {
/// The overarching event type.
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Currency type for the runtime.
type Currency: Currency<Self::AccountId>;
}

// Store the migrated balance snapshot for the darwinia-1.0 chain state.
#[pallet::storage]
#[pallet::getter(fn balance_of)]
pub(super) type Balances<T> = StorageMap<_, Blake2_128Concat, AccountId32, Balance>;

#[pallet::error]
pub enum Error<T> {
/// This account does not exist in the darwinia 1.0 chain state.
AccountNotExist,
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Claim to the new account id.
Claim { old_pub_key: AccountId32, new_pub_key: H160, amount: Balance },
}

#[pallet::genesis_config]
#[cfg_attr(feature = "std", derive(Default))]
pub struct GenesisConfig {
pub migrated_accounts: Vec<(AccountId32, Balance)>,
}

#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
fn build(&self) {
self.migrated_accounts.iter().for_each(|(account, amount)| {
Balances::<T>::insert(account, amount);
});
}
}

#[pallet::call]
impl<T: Config> Pallet<T>
where
AccountIdOf<T>: From<H160>,
BalanceOf<T>: From<Balance>,
{
// since signature and chain_id verification is done in `validate_unsigned`
// we can skip doing it here again.
// TODO: update weight
#[pallet::weight(0)]
pub fn claim_to(
origin: OriginFor<T>,
_chain_id: u64,
old_pub_key: AccountId32,
new_pub_key: H160,
_sig: Signature,
) -> DispatchResult {
ensure_none(origin)?;

let Some(amount) = Balances::<T>::take(&old_pub_key) else {
return Err(Error::<T>::AccountNotExist.into());
};

<T as pallet::Config>::Currency::deposit_creating(&new_pub_key.into(), amount.into());
Self::deposit_event(Event::Claim { old_pub_key, new_pub_key, amount });

Ok(())
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T>
where
AccountIdOf<T>: From<H160>,
BalanceOf<T>: From<Balance>,
{
type Call = Call<T>;

fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let Call::claim_to { chain_id, old_pub_key, new_pub_key, sig } = call else {
return InvalidTransaction::Call.into();
};

if *chain_id != <T as pallet_evm::Config>::ChainId::get() {
return InvalidTransaction::BadProof.into();
}
if !Balances::<T>::contains_key(old_pub_key) {
return InvalidTransaction::BadSigner.into();
}

let message = ClaimMessage::new(
<T as pallet_evm::Config>::ChainId::get(),
old_pub_key,
new_pub_key,
);
if let Ok(signer) = Public::from_slice(old_pub_key.as_ref()) {
let is_valid = sig.verify(&blake2_256(&message.raw_bytes())[..], &signer);

if is_valid {
return ValidTransaction::with_tag_prefix("MigrateClaim")
.priority(TransactionPriority::max_value())
.propagate(true)
.build();
}
}
InvalidTransaction::BadSigner.into()
}
}
}

/// ClaimMessage is the metadata that needs to be signed when the user invokes claim dispatch.
///
/// It consists of three parts, namely the chain_id, the AccountId32 account for darwinia 1.0, and
/// the H160 account for darwinia 2.0.
pub struct ClaimMessage<'m> {
pub chain_id: u64,
pub old_pub_key: &'m AccountId32,
pub new_pub_key: &'m H160,
}

impl<'m> ClaimMessage<'m> {
fn new(chain_id: u64, old_pub_key: &'m AccountId32, new_pub_key: &'m H160) -> Self {
Self { chain_id, old_pub_key, new_pub_key }
}

fn raw_bytes(&self) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice(&self.chain_id.to_be_bytes());
result.extend_from_slice(self.old_pub_key.as_slice());
result.extend_from_slice(self.new_pub_key.as_bytes());
result
}
}
Loading

0 comments on commit a2d5ae8

Please sign in to comment.