Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
556 changes: 278 additions & 278 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions precompiles/proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ where
// Proxies:
// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
handle.record_db_read::<Runtime>(
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 8,
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
)?;
if ProxyPallet::<Runtime>::proxies(origin.clone())
.0
Expand Down Expand Up @@ -340,7 +340,7 @@ where
// Proxies:
// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
handle.record_db_read::<Runtime>(
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 8,
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
)?;
let is_proxy = ProxyPallet::<Runtime>::proxies(real)
.0
Expand Down Expand Up @@ -368,7 +368,7 @@ where
// Proxies:
// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
handle.record_db_read::<Runtime>(
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 8,
28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
)?;
let def =
pallet_proxy::Pallet::<Runtime>::find_proxy(&real_account_id, &who, force_proxy_type)
Expand Down
20 changes: 15 additions & 5 deletions runtime/moonbase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,15 @@ pub mod currency {
}
}

/// Maximum PoV size we support right now.
// Reference: https://github.com/polkadot-fellows/runtimes/pull/553
pub const MAX_POV_SIZE: u32 = 10 * 1024 * 1024;

/// Maximum weight per block
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, u64::MAX)
.saturating_mul(2)
.set_proof_size(relay_chain::MAX_POV_SIZE as u64);
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
MAX_POV_SIZE as u64,
);

pub const MILLISECS_PER_BLOCK: u64 = 6_000;
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
Expand Down Expand Up @@ -393,8 +398,12 @@ pub const GAS_PER_SECOND: u64 = 40_000_000;
/// Approximate ratio of the amount of Weight per Gas.
/// u64 works for approximations because Weight is a very small unit compared to gas.
pub const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND;

/// The highest amount of new storage that can be created in a block (160KB).
/// Originally 40KB, then multiplied by 4 when the block deadline was increased from 500ms to 2000ms.
/// Reference: https://github.com/moonbeam-foundation/moonbeam/blob/master/MBIPS/MBIP-5.md#specification
pub const BLOCK_STORAGE_LIMIT: u64 = 160 * 1024;

parameter_types! {
pub BlockGasLimit: U256
= U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS);
Expand All @@ -414,13 +423,13 @@ parameter_types! {
pub MaximumMultiplier: Multiplier = Multiplier::from(100_000u128);
pub PrecompilesValue: MoonbasePrecompiles<Runtime> = MoonbasePrecompiles::<_>::new();
pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0);
/// The amount of gas per pov. A ratio of 16 if we convert ref_time to gas and we compare
/// The amount of gas per pov. A ratio of 8 if we convert ref_time to gas and we compare
/// it with the pov_size for a block. E.g.
/// ceil(
/// (max_extrinsic.ref_time() / max_extrinsic.proof_size()) / WEIGHT_PER_GAS
/// )
/// We should re-check `xcm_config::Erc20XcmBridgeTransferGasLimit` when changing this value
pub const GasLimitPovSizeRatio: u64 = 16;
pub const GasLimitPovSizeRatio: u64 = 8;
/// The amount of gas per storage (in bytes): BLOCK_GAS_LIMIT / BLOCK_STORAGE_LIMIT
/// (60_000_000 / 160 kb)
pub GasLimitStorageGrowthRatio: u64 = 366;
Expand Down Expand Up @@ -696,6 +705,7 @@ impl pallet_ethereum_xcm::Config for Runtime {
}

parameter_types! {
// Reserved weight is 1/4 of MAXIMUM_BLOCK_WEIGHT
pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent;
Expand Down
2 changes: 1 addition & 1 deletion runtime/moonbase/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ parameter_types! {

// To be able to support almost all erc20 implementations,
// we provide a sufficiently hight gas limit.
pub Erc20XcmBridgeTransferGasLimit: u64 = 800_000;
pub Erc20XcmBridgeTransferGasLimit: u64 = 400_000;
}

impl pallet_erc20_xcm_bridge::Config for Runtime {
Expand Down
8 changes: 2 additions & 6 deletions runtime/moonbase/tests/xcm_mock/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,16 +829,12 @@ impl pallet_timestamp::Config for Runtime {

use sp_core::U256;

const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
/// Block storage limit in bytes. Set to 40 KB.
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;

parameter_types! {
pub BlockGasLimit: U256 = U256::from(u64::MAX);
pub WeightPerGas: Weight = Weight::from_parts(1, 0);
pub GasLimitPovSizeRatio: u64 = {
let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
block_gas_limit.saturating_div(MAX_POV_SIZE)
block_gas_limit.saturating_div(MAX_POV_SIZE as u64)
};
pub GasLimitStorageGrowthRatio: u64 =
BlockGasLimit::get().min(u64::MAX.into()).low_u64().saturating_div(BLOCK_STORAGE_LIMIT);
Expand Down Expand Up @@ -1088,7 +1084,7 @@ pub(crate) fn para_events() -> Vec<RuntimeEvent> {

use frame_support::traits::tokens::{PayFromAccount, UnityAssetBalanceConversion};
use frame_support::traits::{OnFinalize, OnInitialize, UncheckedOnRuntimeUpgrade};
use moonbase_runtime::EvmForeignAssets;
use moonbase_runtime::{EvmForeignAssets, BLOCK_STORAGE_LIMIT, MAX_POV_SIZE};
use pallet_evm::FrameSystemAccountProvider;
use xcm_primitives::AsAssetType;

Expand Down
16 changes: 13 additions & 3 deletions runtime/moonbeam/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,14 @@ pub mod currency {
}
}

/// Maximum PoV size we support right now.
pub const MAX_POV_SIZE: u32 = relay_chain::MAX_POV_SIZE;

/// Maximum weight per block
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, u64::MAX)
.saturating_mul(2)
.set_proof_size(relay_chain::MAX_POV_SIZE as u64);
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
MAX_POV_SIZE as u64,
);

pub const MILLISECS_PER_BLOCK: u64 = 6_000;
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
Expand Down Expand Up @@ -381,6 +385,11 @@ pub const GAS_PER_SECOND: u64 = 40_000_000;
/// u64 works for approximations because Weight is a very small unit compared to gas.
pub const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND;

/// The highest amount of new storage that can be created in a block (160KB).
/// Originally 40KB, then multiplied by 4 when the block deadline was increased from 500ms to 2000ms.
/// Reference: https://github.com/moonbeam-foundation/moonbeam/blob/master/MBIPS/MBIP-5.md#specification
pub const BLOCK_STORAGE_LIMIT: u64 = 160 * 1024;

parameter_types! {
pub BlockGasLimit: U256
= U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS);
Expand Down Expand Up @@ -710,6 +719,7 @@ impl pallet_ethereum_xcm::Config for Runtime {
}

parameter_types! {
// Reserved weight is 1/4 of MAXIMUM_BLOCK_WEIGHT
pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent;
Expand Down
22 changes: 17 additions & 5 deletions runtime/moonriver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,16 @@ pub mod currency {
}
}

/// Maximum PoV size we support right now.
// Kusama relay already supports 10Mb maximum PoV
// Reference: https://github.com/polkadot-fellows/runtimes/pull/553
pub const MAX_POV_SIZE: u32 = 10 * 1024 * 1024;

/// Maximum weight per block
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, u64::MAX)
.saturating_mul(2)
.set_proof_size(relay_chain::MAX_POV_SIZE as u64);
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
MAX_POV_SIZE as u64,
);

pub const MILLISECS_PER_BLOCK: u64 = 6_000;
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
Expand Down Expand Up @@ -386,6 +392,11 @@ pub const GAS_PER_SECOND: u64 = 40_000_000;
/// u64 works for approximations because Weight is a very small unit compared to gas.
pub const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND;

/// The highest amount of new storage that can be created in a block (160KB).
/// Originally 40KB, then multiplied by 4 when the block deadline was increased from 500ms to 2000ms.
/// Reference: https://github.com/moonbeam-foundation/moonbeam/blob/master/MBIPS/MBIP-5.md#specification
pub const BLOCK_STORAGE_LIMIT: u64 = 160 * 1024;

parameter_types! {
pub BlockGasLimit: U256
= U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS);
Expand All @@ -406,13 +417,13 @@ parameter_types! {
pub MaximumMultiplier: Multiplier = Multiplier::from(100_000u128);
pub PrecompilesValue: MoonriverPrecompiles<Runtime> = MoonriverPrecompiles::<_>::new();
pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0);
/// The amount of gas per pov. A ratio of 16 if we convert ref_time to gas and we compare
/// The amount of gas per pov. A ratio of 8 if we convert ref_time to gas and we compare
/// it with the pov_size for a block. E.g.
/// ceil(
/// (max_extrinsic.ref_time() / max_extrinsic.proof_size()) / WEIGHT_PER_GAS
/// )
/// We should re-check `xcm_config::Erc20XcmBridgeTransferGasLimit` when changing this value
pub const GasLimitPovSizeRatio: u64 = 16;
pub const GasLimitPovSizeRatio: u64 = 8;
/// The amount of gas per storage (in bytes): BLOCK_GAS_LIMIT / BLOCK_STORAGE_LIMIT
/// The current definition of BLOCK_STORAGE_LIMIT is 160 KB, resulting in a value of 366.
pub GasLimitStorageGrowthRatio: u64 = 366;
Expand Down Expand Up @@ -689,6 +700,7 @@ impl pallet_ethereum_xcm::Config for Runtime {
}

parameter_types! {
// Reserved weight is 1/4 of MAXIMUM_BLOCK_WEIGHT
pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4);
pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent;
Expand Down
2 changes: 1 addition & 1 deletion runtime/moonriver/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ parameter_types! {

// To be able to support almost all erc20 implementations,
// we provide a sufficiently hight gas limit.
pub Erc20XcmBridgeTransferGasLimit: u64 = 800_000;
pub Erc20XcmBridgeTransferGasLimit: u64 = 400_000;
}

impl pallet_erc20_xcm_bridge::Config for Runtime {
Expand Down
8 changes: 2 additions & 6 deletions runtime/moonriver/tests/xcm_mock/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,16 +820,12 @@ impl pallet_timestamp::Config for Runtime {

use sp_core::U256;

const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
/// Block storage limit in bytes. Set to 160 KB.
const BLOCK_STORAGE_LIMIT: u64 = 160 * 1024;

parameter_types! {
pub BlockGasLimit: U256 = U256::from(u64::MAX);
pub WeightPerGas: Weight = Weight::from_parts(1, 0);
pub GasLimitPovSizeRatio: u64 = {
let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
block_gas_limit.saturating_div(MAX_POV_SIZE)
block_gas_limit.saturating_div(MAX_POV_SIZE as u64)
};
pub GasLimitStorageGrowthRatio: u64 =
BlockGasLimit::get().min(u64::MAX.into()).low_u64().saturating_div(BLOCK_STORAGE_LIMIT);
Expand Down Expand Up @@ -1080,7 +1076,7 @@ pub(crate) fn para_events() -> Vec<RuntimeEvent> {

use frame_support::traits::tokens::{PayFromAccount, UnityAssetBalanceConversion};
use frame_support::traits::{OnFinalize, OnInitialize, UncheckedOnRuntimeUpgrade};
use moonriver_runtime::EvmForeignAssets;
use moonriver_runtime::{EvmForeignAssets, BLOCK_STORAGE_LIMIT, MAX_POV_SIZE};
use pallet_evm::FrameSystemAccountProvider;
use xcm_primitives::AsAssetType;

Expand Down
16 changes: 10 additions & 6 deletions test/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export const RUNTIME_CONSTANTS = {
// minus the block initialization (10%) and minus the extrinsic base cost.
EXTRINSIC_GAS_LIMIT: new RuntimeConstant({ 2900: 52_000_000n, 0: 13_000_000n }),
// Maximum Gas to PoV ratio used in the gasometer
GAS_PER_POV_BYTES: new RuntimeConstant({ 2900: 16n, 0: 4n }),
GAS_PER_POV_BYTES: new RuntimeConstant({ 3600: 8n, 2900: 16n, 0: 4n }),
// Maximum PoV size in bytes allowed by the gasometer for one ethereum transaction
// MAX_ETH_POV_PER_TX = EXTRINSIC_GAS_LIMIT / GAS_PER_POV_BYTES
MAX_ETH_POV_PER_TX: new RuntimeConstant({ 3600: 6_500_000n, 0: 3_250_000n }),
// Storage read/write costs
STORAGE_READ_COST: 41_742_000n,
// Weight to gas conversion ratio
Expand Down Expand Up @@ -143,6 +146,9 @@ export const RUNTIME_CONSTANTS = {
}),
// Maximum Gas to PoV ratio used in the gasometer
GAS_PER_POV_BYTES: new RuntimeConstant({ 3100: 16n, 3000: 8n, 0: 4n }),
// Maximum PoV size in bytes allowed by the gasometer for one ethereum transaction
// MAX_ETH_POV_PER_TX = EXTRINSIC_GAS_LIMIT / GAS_PER_POV_BYTES
MAX_ETH_POV_PER_TX: new RuntimeConstant({ 0: 3_250_000n }),
},
MOONBEAM: {
...MOONBEAM_CONSTANTS,
Expand Down Expand Up @@ -180,14 +186,12 @@ export const RUNTIME_CONSTANTS = {
}),
// Maximum Gas to PoV ratio used in the gasometer
GAS_PER_POV_BYTES: new RuntimeConstant({ 3200: 16n, 3100: 8n, 0: 4n }),
// Maximum PoV size in bytes allowed by the gasometer for one ethereum transaction
// MAX_ETH_POV_PER_TX = EXTRINSIC_GAS_LIMIT / GAS_PER_POV_BYTES
MAX_ETH_POV_PER_TX: new RuntimeConstant({ 0: 3_250_000n }),
},
} as const;

export const GAS_LIMIT_POV_RATIO = 16;

// Maximum PoV size in bytes allowed by the gasometer for one ethereum transaction
export const MAX_ETH_POV_PER_TX = 3_250_000n;

type ConstantStoreType = (typeof RUNTIME_CONSTANTS)["MOONBASE"];

export function ConstantStore(context: GenericContext): ConstantStoreType {
Expand Down
4 changes: 2 additions & 2 deletions test/helpers/xcm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ export const sendCallAsPara = async (
],
weight_limit: {
refTime: 40_000_000_000n,
proofSize: 150_000n,
proofSize: 150_713n,
},
beneficiary: sovereignAccountOfSibling(context, paraId),
})
Expand Down Expand Up @@ -1047,7 +1047,7 @@ export const sendCallAsDescendedOrigin = async (
],
weight_limit: {
refTime: 40_000_000_000n,
proofSize: 150_000n,
proofSize: 150_713n,
},
descend_origin: address,
beneficiary: descndedAddress.descendOriginAddress,
Expand Down
12 changes: 9 additions & 3 deletions test/suites/dev/moonbase/test-fees/test-length-fees2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "@moonbeam-network/api-augment";
import { describeSuite, expect } from "@moonwall/cli";
import { createViemTransaction } from "@moonwall/util";
import { ConstantStore, GAS_LIMIT_POV_RATIO } from "../../../../helpers/constants";
import { ConstantStore } from "../../../../helpers/constants";

describeSuite({
id: "D011607",
Expand All @@ -13,6 +13,7 @@ describeSuite({
title: "should not charge length fee for precompile from Ethereum txn",
test: async () => {
const { specVersion } = await context.polkadotJs().consts.system.version;
const constants = ConstantStore(context);
// we use modexp here because it allows us to send large-ish transactions
const MODEXP_PRECOMPILE_ADDRESS = "0x0000000000000000000000000000000000000005";

Expand All @@ -29,7 +30,7 @@ describeSuite({

const tx = await createViemTransaction(context, {
to: MODEXP_PRECOMPILE_ADDRESS,
gas: BigInt(ConstantStore(context).EXTRINSIC_GAS_LIMIT.get(specVersion.toNumber())),
gas: BigInt(constants.EXTRINSIC_GAS_LIMIT.get(specVersion.toNumber())),
data: ("0x0000000000000000000000000000000000000000000000000000000000000004" + // base
"0000000000000000000000000000000000000000000000000000000000000004" + // exp
"0000000000000000000000000000000000000000000000000000000000000004" + // mod
Expand Down Expand Up @@ -62,7 +63,12 @@ describeSuite({
const modexp_min_cost = 200n * 20n; // see MIN_GAS_COST in frontier's modexp precompile
const entire_fee = non_zero_byte_fee + zero_byte_fee + base_ethereum_fee + modexp_min_cost;
// the gas used should be the maximum of the legacy gas and the pov gas
const expected = BigInt(Math.max(Number(entire_fee), 3821 * GAS_LIMIT_POV_RATIO));
const expected = BigInt(
Math.max(
Number(entire_fee),
3821 * Number(constants.GAS_PER_POV_BYTES.get(specVersion.toNumber()))
)
);
expect(receipt.gasUsed, "gasUsed does not match manual calculation").toBe(expected);
},
});
Expand Down
2 changes: 1 addition & 1 deletion test/suites/dev/moonbase/test-pov/test-evm-over-pov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describeSuite({
let proxyAbi: Abi;
let contracts: HeavyContract[];
let callData: `0x${string}`;
const MAX_CONTRACTS = 20;
const MAX_CONTRACTS = 40;
const EXPECTED_POV_ROUGH = 16_000; // bytes

beforeAll(async () => {
Expand Down
10 changes: 7 additions & 3 deletions test/suites/dev/moonbase/test-pov/test-evm-over-pov2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import "@moonbeam-network/api-augment";
import { beforeAll, deployCreateCompiledContract, describeSuite, expect } from "@moonwall/cli";
import { createEthersTransaction } from "@moonwall/util";
import { type Abi, encodeFunctionData } from "viem";
import { type HeavyContract, deployHeavyContracts } from "../../../../helpers";
import { MAX_ETH_POV_PER_TX } from "../../../../helpers/constants";
import { type HeavyContract, deployHeavyContracts, ConstantStore } from "../../../../helpers";

describeSuite({
id: "D012702",
title: "PoV Limit (3.5Mb in Dev)",
title: "PoV Limit (7.5Mb in Dev)",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let proxyAddress: `0x${string}`;
Expand All @@ -16,8 +15,13 @@ describeSuite({
let callData: `0x${string}`;
let emptyBlockProofSize: bigint;
const MAX_CONTRACTS = 20;
let MAX_ETH_POV_PER_TX: bigint;

beforeAll(async () => {
const specVersion = (await context.polkadotJs().runtimeVersion.specVersion).toNumber();
const constants = ConstantStore(context);
MAX_ETH_POV_PER_TX = constants.MAX_ETH_POV_PER_TX.get(specVersion);

// Create an empty block to estimate empty block proof size
const { block } = await context.createBlock();
// Empty blocks usually do not exceed 50kb
Expand Down
Loading
Loading