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

feature: gas meter #795

Merged
merged 6 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 11 additions & 1 deletion constants.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{
"comment": "Sovereign SDK constants"
"comment": "Sovereign SDK constants",
"gas": {
"multiplier": [1],
"Bank": {
"create_token": [1],
"transfer": [1],
"burn": [1],
"mint": [1],
"freeze": [1]
}
}
}
29 changes: 28 additions & 1 deletion module-system/module-implementations/sov-bank/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod utils;

/// Specifies the call methods using in that module.
pub use call::CallMessage;
use sov_modules_api::{CallResponse, Error, ModuleInfo, WorkingSet};
use sov_modules_api::{CallResponse, Error, GasUnit, ModuleInfo, WorkingSet};
use token::Token;
/// Specifies an interface to interact with tokens.
pub use token::{Amount, Coins};
Expand All @@ -38,6 +38,25 @@ pub struct BankConfig<C: sov_modules_api::Context> {
pub tokens: Vec<TokenConfig<C>>,
}

/// Gas configuration for the bank module
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BankGasConfig<GU: GasUnit> {
/// Gas price multiplier for the create token operation
pub create_token: GU,

/// Gas price multiplier for the transfer operation
pub transfer: GU,

/// Gas price multiplier for the burn operation
pub burn: GU,

/// Gas price multiplier for the mint operation
pub mint: GU,

/// Gas price multiplier for the freeze operation
pub freeze: GU,
}

/// The sov-bank module manages user balances. It provides functionality for:
/// - Token creation.
/// - Token transfers.
Expand All @@ -49,6 +68,9 @@ pub struct Bank<C: sov_modules_api::Context> {
#[address]
pub(crate) address: C::Address,

#[gas]
pub(crate) gas: BankGasConfig<C::GasUnit>,

/// A mapping of addresses to tokens in the sov-bank.
#[state]
pub(crate) tokens: sov_modules_api::StateMap<C::Address, Token<C>>,
Expand Down Expand Up @@ -79,6 +101,7 @@ impl<C: sov_modules_api::Context> sov_modules_api::Module for Bank<C> {
minter_address,
authorized_minters,
} => {
self.charge_gas(working_set, &self.gas.create_token)?;
self.create_token(
token_name,
salt,
Expand All @@ -92,22 +115,26 @@ impl<C: sov_modules_api::Context> sov_modules_api::Module for Bank<C> {
}

call::CallMessage::Transfer { to, coins } => {
self.charge_gas(working_set, &self.gas.create_token)?;
Ok(self.transfer(to, coins, context, working_set)?)
}

call::CallMessage::Burn { coins } => {
self.charge_gas(working_set, &self.gas.burn)?;
Ok(self.burn_from_eoa(coins, context, working_set)?)
}

call::CallMessage::Mint {
coins,
minter_address,
} => {
self.charge_gas(working_set, &self.gas.mint)?;
self.mint_from_eoa(&coins, &minter_address, context, working_set)?;
Ok(CallResponse::default())
}

call::CallMessage::Freeze { token_address } => {
self.charge_gas(working_set, &self.gas.freeze)?;
Ok(self.freeze(token_address, context, working_set)?)
}
}
Expand Down
6 changes: 5 additions & 1 deletion module-system/sov-modules-api/src/default_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use sov_state::{ArrayWitness, DefaultStorageSpec, ZkStorage};
#[cfg(feature = "native")]
use crate::default_signature::private_key::DefaultPrivateKey;
use crate::default_signature::{DefaultPublicKey, DefaultSignature};
use crate::{Address, Context, PublicKey, Spec};
use crate::{Address, Context, PublicKey, Spec, TupleGasUnit};

#[cfg(feature = "native")]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
Expand All @@ -31,6 +31,8 @@ impl Spec for DefaultContext {

#[cfg(feature = "native")]
impl Context for DefaultContext {
type GasUnit = TupleGasUnit<1>;
vlopes11 marked this conversation as resolved.
Show resolved Hide resolved

fn sender(&self) -> &Self::Address {
&self.sender
}
Expand Down Expand Up @@ -58,6 +60,8 @@ impl Spec for ZkDefaultContext {
}

impl Context for ZkDefaultContext {
type GasUnit = TupleGasUnit<2>;

fn sender(&self) -> &Self::Address {
&self.sender
}
Expand Down
92 changes: 92 additions & 0 deletions module-system/sov-modules-api/src/gas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use anyhow::Result;

use core::fmt;

/// A gas unit that provides scalar conversion from complex, multi-dimensional types.
pub trait GasUnit: fmt::Debug + Clone {
/// A zeroed instance of the unit.
const ZEROED: Self;

/// Creates a unit from a multi-dimensional unit with arbitrary dimension.
fn from_arbitrary_dimensions(dimensions: &[u64]) -> Self;
vlopes11 marked this conversation as resolved.
Show resolved Hide resolved

/// Converts the unit into a scalar value, given a price.
fn value(&self, price: &Self) -> u64;
}

/// A multi-dimensional gas unit.
pub type TupleGasUnit<const N: usize> = [u64; N];

impl<const N: usize> GasUnit for TupleGasUnit<N> {
const ZEROED: Self = [0; N];

fn from_arbitrary_dimensions(dimensions: &[u64]) -> Self {
// as demonstrated on the link below, the compiler can easily optimize the conversion as if
// it is a transparent type.
//
// https://rust.godbolt.org/z/rPhaxnPEY
let mut unit = Self::ZEROED;
unit.iter_mut()
.zip(dimensions.iter().copied())
.for_each(|(a, b)| *a = b);
unit
}

fn value(&self, price: &Self) -> u64 {
self.iter()
.zip(price.iter().copied())
.map(|(a, b)| a.saturating_mul(b))
.fold(0, |a, b| a.saturating_add(b))
}
}

/// A gas meter.
pub struct GasMeter<GU>
where
GU: GasUnit,
{
remaining_funds: u64,
gas_price: GU,
}

impl<GU> Default for GasMeter<GU>
where
GU: GasUnit,
{
fn default() -> Self {
Self {
remaining_funds: 0,
gas_price: GU::ZEROED,
}
}
}

impl<GU> GasMeter<GU>
where
GU: GasUnit,
{
/// Creates a new instance of the gas meter with the provided price.
pub fn new(remaining_funds: u64, gas_price: GU) -> Self {
Self {
remaining_funds,
gas_price,
}
}

/// Returns the remaining gas funds.
pub const fn remaining_funds(&self) -> u64 {
self.remaining_funds
}

/// Deducts the provided gas unit from the remaining funds, computing the scalar value of the
/// funds from the price of the instance.
pub fn charge_gas(&mut self, gas: &GU) -> Result<()> {
let gas = gas.value(&self.gas_price);
self.remaining_funds = self
.remaining_funds
.checked_sub(gas)
.ok_or_else(|| anyhow::anyhow!("Not enough gas"))?;

Ok(())
}
}
17 changes: 17 additions & 0 deletions module-system/sov-modules-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod default_signature;
mod dispatch;
mod encode;
mod error;
mod gas;
pub mod hooks;

#[cfg(feature = "macros")]
Expand Down Expand Up @@ -45,6 +46,7 @@ use digest::Digest;
pub use dispatch::CliWallet;
pub use dispatch::{DispatchCall, EncodeCall, Genesis};
pub use error::Error;
pub use gas::{GasUnit, TupleGasUnit};
pub use prefix::Prefix;
pub use response::CallResponse;
#[cfg(feature = "native")]
Expand Down Expand Up @@ -250,6 +252,9 @@ pub trait Spec {
/// instance of the state transition function. By making modules generic over a `Context`, developers
/// can easily update their cryptography to conform to the needs of different zk-proof systems.
pub trait Context: Spec + Clone + Debug + PartialEq + 'static {
/// Gas unit for the gas price computation.
type GasUnit: GasUnit;

/// Sender of the transaction.
fn sender(&self) -> &Self::Address;

Expand Down Expand Up @@ -305,6 +310,17 @@ pub trait Module {
) -> Result<CallResponse, Error> {
unreachable!()
}

/// Attempts to charge the provided amount of gas from the working set.
///
/// The scalar gas value will be computed from the price defined on the working set.
fn charge_gas(
&self,
vlopes11 marked this conversation as resolved.
Show resolved Hide resolved
working_set: &mut WorkingSet<Self::Context>,
gas: &<Self::Context as Context>::GasUnit,
) -> anyhow::Result<()> {
working_set.charge_gas(gas)
}
}

/// A [`Module`] that has a well-defined and known [JSON
Expand All @@ -321,6 +337,7 @@ pub trait ModuleCallJsonSchema: Module {

/// Every module has to implement this trait.
pub trait ModuleInfo {
/// Execution context.
type Context: Context;

/// Returns address of the module.
Expand Down
19 changes: 19 additions & 0 deletions module-system/sov-modules-api/src/state/scratchpad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sov_state::codec::{EncodeKeyLike, StateCodec, StateValueCodec};
use sov_state::storage::{Storage, StorageKey, StorageValue};
use sov_state::{OrderedReadsAndWrites, Prefix, StorageInternalCache};

use crate::gas::GasMeter;
use crate::{Context, Spec};

/// A working set accumulates reads and writes on top of the underlying DB,
Expand Down Expand Up @@ -103,6 +104,7 @@ impl<C: Context> StateCheckpoint<C> {
delta: RevertableWriter::new(self.delta),
accessory_delta: RevertableWriter::new(self.accessory_delta),
events: Default::default(),
gas_meter: GasMeter::default(),
}
}

Expand Down Expand Up @@ -186,6 +188,7 @@ pub struct WorkingSet<C: Context> {
delta: RevertableWriter<Delta<C::Storage>>,
accessory_delta: RevertableWriter<AccessoryDelta<C::Storage>>,
events: Vec<Event>,
gas_meter: GasMeter<C::GasUnit>,
}

impl<C: Context> StateReaderAndWriter for WorkingSet<C> {
Expand Down Expand Up @@ -352,6 +355,22 @@ impl<C: Context> WorkingSet<C> {
pub fn backing(&self) -> &<C as Spec>::Storage {
&self.delta.inner.inner
}

/// Returns the remaining gas funds.
pub const fn gas_remaining_funds(&self) -> u64 {
self.gas_meter.remaining_funds()
}

/// Overrides the current gas settings with the provided values.
pub fn set_gas(&mut self, funds: u64, gas_price: C::GasUnit) {
self.gas_meter = GasMeter::new(funds, gas_price);
}

/// Attempts to charge the provided gas unit from the gas meter, using the internal price to
/// compute the scalar value.
pub fn charge_gas(&mut self, gas: &C::GasUnit) -> anyhow::Result<()> {
self.gas_meter.charge_gas(gas)
}
}

pub(crate) trait StateReaderAndWriter {
Expand Down
2 changes: 1 addition & 1 deletion module-system/sov-modules-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use proc_macro::TokenStream;
use rpc::ExposeRpcMacro;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(ModuleInfo, attributes(state, module, address))]
#[proc_macro_derive(ModuleInfo, attributes(state, module, address, gas))]
pub fn module_info(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);

Expand Down
Loading