Skip to content

Commit

Permalink
Add v10 migration - New deposit_account field to ContractInfo (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
HenriqueNogara authored Aug 4, 2023
1 parent 020f51a commit f66cfc8
Showing 1 changed file with 191 additions and 12 deletions.
203 changes: 191 additions & 12 deletions frame/contracts/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight};
use codec::{Decode, Encode};
use frame_support::{
codec,
pallet_prelude::*,
storage::migration,
storage_alias,
traits::{Get, OnRuntimeUpgrade},
Identity, Twox64Concat,
};
use sp_runtime::traits::Saturating;
use sp_std::{marker::PhantomData, prelude::*};
use core::cmp::{max, min};
use frame_support::{codec, Identity, Twox64Concat, storage_alias, LOG_TARGET};
use frame_support::pallet_prelude::*;
use frame_support::storage::migration;
use frame_support::traits::fungible::Inspect;
use frame_support::traits::{Get, OnRuntimeUpgrade, ReservableCurrency, Currency, ExistenceRequirement};
use sp_core::hexdisplay::HexDisplay;
use sp_runtime::traits::{Saturating, Zero};
use sp_runtime::Perbill;
use sp_std::marker::PhantomData;
use sp_std::prelude::*;
use sp_std::ops::Deref;

use crate::{AccountIdOf, BalanceOf, CodeHash, Config, Pallet, TrieId, Weight};
use crate::address::AddressGenerator;

/// Performs all necessary migrations based on `StorageVersion`.
pub struct Migration<T: Config>(PhantomData<T>);
Expand Down Expand Up @@ -59,7 +63,11 @@ impl<T: Config> OnRuntimeUpgrade for Migration<T> {
v9::migrate::<T>(&mut weight);
}

StorageVersion::new(9).put::<Pallet<T>>();
if version < 10 {
v10::migrate::<T>(&mut weight);
}

StorageVersion::new(10).put::<Pallet<T>>();
weight.saturating_accrue(T::DbWeight::get().writes(1));

weight
Expand Down Expand Up @@ -400,6 +408,142 @@ mod v9 {
}
}

mod v10 {
use super::*;
use v8::ContractInfo as OldContractInfo;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
pub trie_id: TrieId,
deposit_account: DepositAccount<T>,
pub code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
storage_item_deposit: BalanceOf<T>,
storage_base_deposit: BalanceOf<T>,
}

#[storage_alias]
pub type ContractInfoOf<T: Config, V> =
StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, V>;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct DepositAccount<T: Config>(AccountIdOf<T>);

impl<T: Config> Deref for DepositAccount<T> {
type Target = AccountIdOf<T>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

pub fn migrate<T: Config>(weight: &mut Weight) {
<ContractInfoOf<T, ContractInfo<T>>>::translate(|account, contract: OldContractInfo<T>| {
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
// Get the new deposit account address
let deposit_account: DepositAccount<T> =
DepositAccount(T::AddressGenerator::deposit_address(&account));

let min_balance = Pallet::<T>::min_balance();
// Calculate the existing deposit, that should be reserved on the contract account
let old_deposit = contract
.storage_base_deposit
.saturating_add(contract.storage_item_deposit)
.saturating_add(contract.storage_byte_deposit);

// Unreserve the existing deposit
// Note we can't use repatriate_reserve, because it only works with existing accounts
let remaining = T::Currency::unreserve(&account, old_deposit);
if !remaining.is_zero() {
log::warn!(
target: LOG_TARGET,
"Partially unreserved. Remaining {:?} out of {:?} asked",
remaining,
old_deposit
);
}

// Attempt to transfer the old deposit to the deposit account.
let amount = old_deposit
.saturating_sub(min_balance)
.min(T::Currency::reducible_balance(&account, true));

let new_deposit = T::Currency::transfer_no_cdd(
&account,
&deposit_account,
amount,
ExistenceRequirement::KeepAlive,
)
.map(|_| {
log::debug!(
target: LOG_TARGET,
"Transferred deposit ({:?}) to deposit account",
amount
);
amount
})
// If it fails we fallback to minting the ED.
.unwrap_or_else(|err| {
log::error!(target: LOG_TARGET, "Failed to transfer the base deposit, reason: {:?}", err);
T::Currency::deposit_creating(&deposit_account, min_balance);
min_balance
});

// Calculate the new base_deposit to store in the contract:
// Ideally, it should be the same as the old one
// Ideally, it should be at least 2xED (for the contract and deposit accounts).
// It can't be more than the `new_deposit`.
let new_base_deposit = min(
max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)),
new_deposit,
);

// Calculate the ratio to adjust storage_byte and storage_item deposits.
let new_deposit_without_base = new_deposit.saturating_sub(new_base_deposit);
let old_deposit_without_base =
old_deposit.saturating_sub(contract.storage_base_deposit);
let ratio = Perbill::from_rational(new_deposit_without_base, old_deposit_without_base);

// Calculate the new storage deposits based on the ratio
let storage_byte_deposit = ratio.mul_ceil(contract.storage_byte_deposit);
let storage_item_deposit = ratio.mul_ceil(contract.storage_item_deposit);

// Recalculate the new base deposit, instead of using new_base_deposit to avoid rounding
// errors
let storage_base_deposit = new_deposit
.saturating_sub(storage_byte_deposit)
.saturating_sub(storage_item_deposit);

// Reads: One read for each storage item plus the contract info itself.
// Writes: Only the new contract info.
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));

Some(ContractInfo {
trie_id: contract.trie_id,
deposit_account,
code_hash: contract.code_hash,
storage_bytes: contract.storage_bytes,
storage_items: contract.storage_items,
storage_byte_deposit,
storage_item_deposit,
storage_base_deposit,
})
});
}

#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let sample: Vec<_> = ContractInfoOf::<T>::iter().take(10).collect();

log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
Ok(sample.encode())
}
}

// Post checks always need to be run against the latest storage version. This is why we
// do not scope them in the per version modules. They always need to be ported to the latest
// version.
Expand Down Expand Up @@ -431,6 +575,10 @@ mod post_checks {
v9::<T>()?;
}

if old_version < 10 {
v10::<T>()?;
}

Ok(())
}

Expand Down Expand Up @@ -470,4 +618,35 @@ mod post_checks {
}
Ok(())
}

fn v10<T: Config>() -> Result<(), &'static str> {
let sample = <Vec<(T::AccountId, old::ContractInfo<T>)> as Decode>::decode(&mut &state[..])
.expect("pre_upgrade_step provides a valid state; qed");

log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
for (account, old_contract) in sample {
log::debug!(target: LOG_TARGET, "===");
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
let contract = ContractInfoOf::<T>::get(&account).unwrap();
ensure!(old_contract.trie_id == contract.trie_id, "invalid trie_id");
ensure!(old_contract.code_hash == contract.code_hash, "invalid code_hash");
ensure!(old_contract.storage_bytes == contract.storage_bytes, "invalid storage_bytes");
ensure!(old_contract.storage_items == contract.storage_items, "invalid storage_items");

let deposit =
<<T as Config>::Currency as frame_support::traits::Currency<_>>::total_balance(
&contract.deposit_account,
);
ensure!(
deposit ==
contract
.storage_base_deposit
.saturating_add(contract.storage_item_deposit)
.saturating_add(contract.storage_byte_deposit),
"deposit mismatch"
);
}

Ok(())
}
}

0 comments on commit f66cfc8

Please sign in to comment.