Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foreign Assets Migration #3020

Merged
merged 35 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d6bcca7
add a call to freeze asset and create smart contract
ahmadkaouk Oct 21, 2024
161b4bd
add a storage value to track foreign asset migration status
ahmadkaouk Oct 22, 2024
8e5ebe5
add a call to migrate balances
ahmadkaouk Oct 23, 2024
7a20496
migrate approvals
ahmadkaouk Nov 6, 2024
3f152f7
update migration calls
ahmadkaouk Nov 11, 2024
eaf41f6
add test for start_foreign_asset_migration call
ahmadkaouk Nov 14, 2024
80eee6a
add test for balances migrations
ahmadkaouk Nov 17, 2024
7ff1cf2
add tests for approvals migration
ahmadkaouk Nov 17, 2024
4a3589f
add test for finish migration
ahmadkaouk Nov 18, 2024
0663797
add typescript tests
ahmadkaouk Nov 22, 2024
674ff42
Merge branch 'master' into ahmad-foreign-assets-migration
ahmadkaouk Nov 22, 2024
6218511
update polkadot-sdk pin
ahmadkaouk Nov 22, 2024
1c795d8
add benchmarking scenarios
ahmadkaouk Nov 24, 2024
ca7fc5f
update typescript tests
ahmadkaouk Nov 25, 2024
26e2c8b
update benchmark scenarios
ahmadkaouk Nov 25, 2024
86c46e1
fix revet when minting and approving
ahmadkaouk Nov 28, 2024
f21b22c
add local genretad weights
ahmadkaouk Nov 29, 2024
54b67db
fix format
ahmadkaouk Nov 29, 2024
59bb6d4
format
ahmadkaouk Nov 29, 2024
328b3d0
fix test build
ahmadkaouk Nov 29, 2024
537ba73
fix format
ahmadkaouk Nov 29, 2024
029dc8e
Merge branch 'master' into ahmad-foreign-assets-migration
ahmadkaouk Nov 29, 2024
478e2fc
fix test
ahmadkaouk Nov 29, 2024
ea64612
address comments and add more tests
ahmadkaouk Dec 3, 2024
181a7c2
Update pallets/moonbeam-lazy-migrations/src/lib.rs
ahmadkaouk Dec 3, 2024
63dc1bc
Apply suggestions from code review
ahmadkaouk Dec 3, 2024
7b8a5f6
allow multiple assets to be migrated
ahmadkaouk Dec 3, 2024
758efe9
update call to approve migration of assets
ahmadkaouk Dec 4, 2024
e9493ce
update benchmarks
ahmadkaouk Dec 4, 2024
9933293
fix rust tests
ahmadkaouk Dec 4, 2024
82e8a7f
fix typescript tests
ahmadkaouk Dec 4, 2024
9b12c5d
update origin
ahmadkaouk Dec 4, 2024
e7195b5
fix ci
ahmadkaouk Dec 4, 2024
445d85d
fix benchmarks
ahmadkaouk Dec 4, 2024
ebcbe01
Merge branch 'master' into ahmad-foreign-assets-migration
ahmadkaouk Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
565 changes: 286 additions & 279 deletions Cargo.lock

Large diffs are not rendered by default.

50 changes: 48 additions & 2 deletions pallets/moonbeam-foreign-assets/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ use xcm::latest::Error as XcmError;
const ERC20_CALL_MAX_CALLDATA_SIZE: usize = 4 + 32 + 32; // selector + address + uint256
const ERC20_CREATE_MAX_CALLDATA_SIZE: usize = 16 * 1024; // 16Ko

// Hardcoded gas limits (from manueal binary search)
const ERC20_CREATE_GAS_LIMIT: u64 = 3_367_000; // highest failure: 3_366_000
// Hardcoded gas limits (from manual binary search)
const ERC20_CREATE_GAS_LIMIT: u64 = 3_410_000; // highest failure: 3_406_000
pub(crate) const ERC20_BURN_FROM_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
pub(crate) const ERC20_MINT_INTO_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
const ERC20_PAUSE_GAS_LIMIT: u64 = 150_000; // highest failure: 149_500
pub(crate) const ERC20_TRANSFER_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
pub(crate) const ERC20_APPROVE_GAS_LIMIT: u64 = 154_000; // highest failure: 153_000
const ERC20_UNPAUSE_GAS_LIMIT: u64 = 150_000; // highest failure: 149_500

pub enum EvmError {
Expand Down Expand Up @@ -247,6 +248,51 @@ impl<T: crate::Config> EvmCaller<T> {
Ok(())
}

pub(crate) fn erc20_approve(
erc20_contract_address: H160,
owner: H160,
spender: H160,
amount: U256,
) -> Result<(), EvmError> {
let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
// Selector
input.extend_from_slice(&keccak256!("approve(address,uint256)")[..4]);
// append spender address
input.extend_from_slice(H256::from(spender).as_bytes());
// append amount to be approved
input.extend_from_slice(H256::from_uint(&amount).as_bytes());
let weight_limit: Weight =
T::GasWeightMapping::gas_to_weight(ERC20_APPROVE_GAS_LIMIT, true);

let exec_info = T::EvmRunner::call(
owner,
erc20_contract_address,
input,
U256::default(),
ERC20_APPROVE_GAS_LIMIT,
None,
None,
None,
Default::default(),
false,
false,
Some(weight_limit),
Some(0),
&<T as pallet_evm::Config>::config(),
)
.map_err(|_| EvmError::EvmCallFail)?;

ensure!(
matches!(
exec_info.exit_reason,
ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
),
EvmError::EvmCallFail
);

Ok(())
}

pub(crate) fn erc20_burn_from(
erc20_contract_address: H160,
who: H160,
Expand Down
23 changes: 22 additions & 1 deletion pallets/moonbeam-foreign-assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub mod pallet {

/// Compute asset contract address from asset id
#[inline]
pub(crate) fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
pub fn contract_address_from_asset_id(asset_id: AssetId) -> H160 {
let mut buffer = [0u8; 20];
buffer[..4].copy_from_slice(&FOREIGN_ASSETS_PREFIX);
buffer[4..].copy_from_slice(&asset_id.to_be_bytes());
Expand All @@ -259,6 +259,27 @@ pub mod pallet {
})
.map_err(Into::into)
}

/// Aprrove a spender to spend a certain amount of tokens from the owner account
pub fn approve(
asset_id: AssetId,
owner: T::AccountId,
spender: T::AccountId,
amount: U256,
) -> Result<(), evm::EvmError> {
// We perform the evm call in a storage transaction to ensure that if it fail
// any contract storage changes are rolled back.
frame_support::storage::with_storage_layer(|| {
RomarQ marked this conversation as resolved.
Show resolved Hide resolved
EvmCaller::<T>::erc20_approve(
Self::contract_address_from_asset_id(asset_id),
T::AccountIdToH160::convert(owner),
T::AccountIdToH160::convert(spender),
amount,
)
})
.map_err(Into::into)
}

pub fn weight_of_erc20_burn() -> Weight {
T::GasWeightMapping::gas_to_weight(evm::ERC20_BURN_FROM_GAS_LIMIT, true)
}
Expand Down
12 changes: 12 additions & 0 deletions pallets/moonbeam-lazy-migrations/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-scheduler = { workspace = true }
pallet-assets = { workspace = true }
pallet-asset-manager = { workspace = true }
pallet-balances = { workspace = true }
pallet-moonbeam-foreign-assets = { workspace = true }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
xcm = { workspace = true }
xcm-primitives = { workspace = true }

environmental = { workspace = true }

# Frontier
pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] }
Expand All @@ -34,13 +40,15 @@ frame-benchmarking = { workspace = true, optional = true }
frame-benchmarking = { workspace = true, features = ["std"] }
pallet-balances = { workspace = true, features = ["std", "insecure_zero_ed"] }
pallet-timestamp = { workspace = true, features = ["std"] }
precompile-utils = { workspace = true, features = ["std"] }
rlp = { workspace = true, features = ["std"] }
sp-io = { workspace = true, features = ["std"] }

[features]
default = ["std"]
runtime-benchmarks = ["frame-benchmarking"]
std = [
"environmental/std",
"pallet-balances/std",
"frame-support/std",
"frame-system/std",
Expand All @@ -52,7 +60,11 @@ std = [
"pallet-evm/std",
"pallet-timestamp/std",
"pallet-assets/std",
"pallet-moonbeam-foreign-assets/std",
"pallet-asset-manager/std",
"precompile-utils/std",
"cumulus-primitives-storage-weight-reclaim/std",
"rlp/std",
"xcm-primitives/std",
]
try-runtime = ["frame-support/try-runtime"]
183 changes: 183 additions & 0 deletions pallets/moonbeam-lazy-migrations/src/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2024 Moonbeam Foundation.
// This file is part of Moonbeam.

// Moonbeam 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.

// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
// #![cfg(feature = "runtime-benchmarks")]

use crate::{foreign_asset::ForeignAssetMigrationStatus, Call, Config, Pallet};
use frame_benchmarking::{account, benchmarks};
use frame_support::traits::Currency;
use frame_system::RawOrigin;
use sp_core::{Get, U256};
use sp_runtime::traits::StaticLookup;
use sp_runtime::Saturating;
use sp_std::vec::Vec;
use xcm::latest::prelude::*;

fn setup_foreign_asset<T: Config>(n_accounts: u32) -> T::AssetIdParameter {
let asset_type = T::ForeignAssetType::default();
let metadata = T::AssetRegistrarMetadata::default();
let asset_id = asset_type.clone().into();

let caller: T::AccountId = pallet_asset_manager::Pallet::<T>::account_id();
let caller_lookup = T::Lookup::unlookup(caller.clone());
let root: T::RuntimeOrigin = RawOrigin::Root.into();

// Register in asset manager
let _ = pallet_asset_manager::Pallet::<T>::register_foreign_asset(
root.clone(),
asset_type,
metadata,
<T as pallet_asset_manager::Config>::Balance::from(1u32),
true,
)
.unwrap();

let _ = <T as pallet_assets::Config>::Currency::deposit_creating(
&caller,
<T as pallet_assets::Config>::MetadataDepositBase::get()
.saturating_add(
<T as pallet_assets::Config>::MetadataDepositPerByte::get()
.saturating_mul((T::StringLimit::get() as u32).into()),
)
.saturating_mul(2u32.into()),
);

let dummy = Vec::from_iter((0..T::StringLimit::get() as usize).map(|_| 0u8));
let _ = pallet_assets::Pallet::<T>::set_metadata(
RawOrigin::Signed(caller.clone()).into(),
asset_id.clone().into(),
dummy.clone(),
dummy,
18,
)
.unwrap();

// Create approval
pallet_assets::Pallet::<T>::mint(
RawOrigin::Signed(caller.clone()).into(),
asset_id.clone().into(),
caller_lookup,
(100 * (n_accounts + 1)).into(),
)
.unwrap();

// Setup n accounts with balances and approvals
for i in 0..n_accounts {
let user: T::AccountId = account("user", i, 0);
let user_lookup = T::Lookup::unlookup(user.clone());

// Mint assets
let _ = pallet_assets::Pallet::<T>::mint(
RawOrigin::Signed(caller.clone()).into(),
asset_id.clone().into(),
user_lookup,
100u32.into(),
)
.unwrap();

let spender: T::AccountId = account("spender", i, 0);
let spender_lookup = T::Lookup::unlookup(spender.clone());
let enough = <T as pallet_assets::Config>::Currency::minimum_balance();
<T as pallet_assets::Config>::Currency::make_free_balance_be(&spender, enough);

let _ = pallet_assets::Pallet::<T>::approve_transfer(
RawOrigin::Signed(caller.clone()).into(),
asset_id.clone().into(),
spender_lookup,
5u32.into(),
)
.unwrap();
}

asset_id.into()
}

benchmarks! {
where_clause {
where
<T as pallet_assets::Config>::Balance: Into<U256>,
T::ForeignAssetType: Into<Option<Location>>,
}
start_foreign_assets_migration {
let asset_id = setup_foreign_asset::<T>(1);
}: _(RawOrigin::Root, asset_id.into())
verify {
assert!(matches!(
crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
ForeignAssetMigrationStatus::Migrating(_)
));
}

migrate_foreign_asset_balances {
let n in 1 .. 1000u32;
let asset_id = setup_foreign_asset::<T>(n);
Pallet::<T>::start_foreign_assets_migration(
RawOrigin::Root.into(),
asset_id.into()
)?;
}: _(RawOrigin::Signed(account("caller", 0, 0)), n + 1)
verify {
match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
ForeignAssetMigrationStatus::Migrating(info) => {
assert_eq!(info.remaining_balances, 0);
},
_ => panic!("Expected Migrating status"),
}
}

migrate_foreign_asset_approvals {
let n in 1 .. 1000u32;
let asset_id = setup_foreign_asset::<T>(n);
Pallet::<T>::start_foreign_assets_migration(
RawOrigin::Root.into(),
asset_id.into()
)?;
Pallet::<T>::migrate_foreign_asset_balances(
RawOrigin::Signed(account("caller", 0, 0)).into(),
n + 1
)?;
}: _(RawOrigin::Signed(account("caller", 0, 0)), n)
verify {
match crate::pallet::ForeignAssetMigrationStatusValue::<T>::get() {
ForeignAssetMigrationStatus::Migrating(info) => {
assert_eq!(info.remaining_approvals, 0);
},
_ => panic!("Expected Migrating status"),
}
}

finish_foreign_assets_migration {
let n = 100u32;
let asset_id = setup_foreign_asset::<T>(n);
Pallet::<T>::start_foreign_assets_migration(
RawOrigin::Root.into(),
asset_id.into()
)?;
Pallet::<T>::migrate_foreign_asset_balances(
RawOrigin::Signed(account("caller", 0, 0)).into(),
n + 1
)?;
Pallet::<T>::migrate_foreign_asset_approvals(
RawOrigin::Signed(account("caller", 0, 0)).into(),
n + 1
)?;
}: _(RawOrigin::Signed(account("caller", 0, 0)))
verify {
assert_eq!(
crate::pallet::ForeignAssetMigrationStatusValue::<T>::get(),
ForeignAssetMigrationStatus::Idle
);
}
}
Loading
Loading