Skip to content

Commit

Permalink
Merge branch 'nightly' into blaze/accounts_genesis_file
Browse files Browse the repository at this point in the history
  • Loading branch information
bkolad authored Sep 29, 2023
2 parents 1e86120 + ca038a7 commit 25ce0c1
Show file tree
Hide file tree
Showing 16 changed files with 574 additions and 109 deletions.
11 changes: 10 additions & 1 deletion constants.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{
"comment": "Sovereign SDK constants"
"comment": "Sovereign SDK constants",
"gas": {
"Bank": {
"create_token": [4, 4],
"transfer": [5, 5],
"burn": [2, 2],
"mint": [2, 2],
"freeze": [1, 1]
}
}
}
5 changes: 0 additions & 5 deletions examples/demo-prover/methods/guest-celestia/Cargo.lock

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

5 changes: 0 additions & 5 deletions examples/demo-prover/methods/guest-mock/Cargo.lock

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

68 changes: 65 additions & 3 deletions examples/demo-rollup/tests/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ impl TestClient {
Ok(receipt_req)
}

async fn deploy_contract_call(&self) -> Result<Bytes, Box<dyn std::error::Error>> {
let req = Eip1559TransactionRequest::new()
.from(self.from_addr)
.chain_id(self.chain_id)
.nonce(0u64)
.max_priority_fee_per_gas(10u64)
.max_fee_per_gas(MAX_FEE_PER_GAS)
.gas(900000u64)
.data(self.contract.byte_code());

let typed_transaction = TypedTransaction::Eip1559(req);

let receipt_req = self.eth_call(typed_transaction, None).await?;

Ok(receipt_req)
}

async fn set_value_unsigned(
&self,
contract_address: H160,
Expand Down Expand Up @@ -236,6 +253,31 @@ impl TestClient {
chain_id.as_u64()
}

async fn eth_get_balance(&self, address: Address) -> ethereum_types::U256 {
self.http_client
.request("eth_getBalance", rpc_params![address, "latest"])
.await
.unwrap()
}

async fn eth_get_storage_at(
&self,
address: Address,
index: ethereum_types::U256,
) -> ethereum_types::U256 {
self.http_client
.request("eth_getStorageAt", rpc_params![address, index, "latest"])
.await
.unwrap()
}

async fn eth_get_code(&self, address: Address) -> Bytes {
self.http_client
.request("eth_getCode", rpc_params![address, "latest"])
.await
.unwrap()
}

async fn eth_get_transaction_count(&self, address: Address) -> u64 {
let count: ethereum_types::U64 = self
.http_client
Expand Down Expand Up @@ -289,17 +331,30 @@ impl TestClient {
let nonce = self.eth_get_transaction_count(self.from_addr).await;
assert_eq!(0, nonce);

let contract_address = {
// Balance should be > 0 in genesis
let balance = self.eth_get_balance(self.from_addr).await;
assert!(balance > ethereum_types::U256::zero());

let (contract_address, runtime_code) = {
let runtime_code = self.deploy_contract_call().await?;

let deploy_contract_req = self.deploy_contract().await?;
self.send_publish_batch_request().await;

deploy_contract_req
let contract_address = deploy_contract_req
.await?
.unwrap()
.contract_address
.unwrap()
.unwrap();

(contract_address, runtime_code)
};

// Assert contract deployed correctly
let code = self.eth_get_code(contract_address).await;
// code has natural following 0x00 bytes, so we need to trim it
assert_eq!(code.to_vec()[..runtime_code.len()], runtime_code.to_vec());

// Nonce should be 1 after the deploy
let nonce = self.eth_get_transaction_count(self.from_addr).await;
assert_eq!(1, nonce);
Expand All @@ -320,6 +375,13 @@ impl TestClient {
let get_arg = self.query_contract(contract_address).await?;
assert_eq!(set_arg, get_arg.as_u32());

// Assert storage slot is set
let storage_slot = 0x0;
let storage_value = self
.eth_get_storage_at(contract_address, storage_slot.into())
.await;
assert_eq!(storage_value, ethereum_types::U256::from(set_arg));

// Check that the second block has published
// None should return the latest block
// It should have a single transaction, setting the value
Expand Down
105 changes: 105 additions & 0 deletions module-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,111 @@ This has several consequences. First, it means that modules are always cheap to
always yields the same result as calling `MyModule::new()`. Finally, it means that every method of the module which reads or
modifies state needs to take a `WorkingSet` as an argument.

### Gas configuration

The module might contain a field for the gas configuration. If annotated with `#[gas]` under a struct that derives `ModuleInfo`, it will attempt to read a `constants.json` file from the root of the project, and inject it into the `Default::default()` implementation of the module.

Here is an example `constants.json` file:

```json
{
"gas": {
"create_token": 4,
"transfer": 5,
"burn": 2,
"mint": 2,
"freeze": 1
}
}
```

The `ModuleInfo` macro will look for a `gas` field inside the JSON, that must be an object, and will look for the name of the module inside of the `gas` object. If present, it will parse that object as gas configuration; otherwise, it will parse the `gas` object directly. On the example above, it will attempt to parse a structure that looks like this:

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BankGasConfig<GU: GasUnit> {
pub create_token: GU,
pub transfer: GU,
pub burn: GU,
pub mint: GU,
pub freeze: GU,
}
```

The `GasUnit` generic type will be defined by the runtime `Context`. For `DefaultContext`, we use `TupleGasUnit<2>` - that is, a gas unit with a two dimensions. The same setup is defined for `ZkDefaultContext`. Here is an example of a `constants.json` file, specific to the `Bank` module:

```json
{
"gas": {
"comment": "this field will be ignored, as there is a matching module field",
"Bank": {
"create_token": [4, 19],
"transfer": [5, 25],
"burn": [2, 7],
"mint": [2, 6],
"freeze": [1, 4]
}
}
}
```

As you can see above, the fields can be either array, numeric, or boolean. If boolean, it will be converted to either `0` or `1`. If array, each element is expected to be either a numeric or boolean. The example above will create a gas unit of two dimensions. If the `Context` requires less dimensions than available, it will pick the first ones of relevance, and ignore the rest. That is: with a `Context` of one dimension, , the effective config will be expanded to:

```rust
BankGasConfig {
create_token: [4],
transfer: [5],
burn: [2],
mint: [2],
freeze: [1],
}
```

In order to charge gas from the working set, the function `charge_gas` can be used.

```rust
fn call(
&self,
msg: Self::CallMessage,
context: &Self::Context,
working_set: &mut WorkingSet<C>,
) -> Result<sov_modules_api::CallResponse, Error> {
match msg {
call::CallMessage::CreateToken {
salt,
token_name,
initial_balance,
minter_address,
authorized_minters,
} => {
self.charge_gas(working_set, &self.gas.create_token)?;
// Implementation elided...
}
```

On the example above, we charge the configured unit from the working set. Concretely, we will charge a unit of `[4, 19]` from both `DefaultContext` and `ZkDefaultContext`. The working set will be the responsible to perform a scalar conversion from the dimensions to a single funds value. It will perform an inner product of the loaded price, with the provided unit.

Let's assume we have a working set with the loaded price `[3, 2]`. The charged gas of the operation above will be `[3] · [4] = 3 × 4 = 12` for a single dimension context, and `[3, 2] · [4, 19] = 3 × 4 + 2 × 19 = 50` for both `DefaultContext` and `ZkDefaultContext`. This approach is intended to unlock [Dynamic Pricing](https://arxiv.org/abs/2208.07919).

The aforementioned `Bank` struct, with the gas configuration, will look like this:

```rust
#[derive(ModuleInfo)]
pub struct Bank<C: sov_modules_api::Context> {
/// The address of the bank module.
#[address]
pub(crate) address: C::Address,

/// The gas configuration of the sov-bank module.
#[gas]
pub(crate) gas: BankGasConfig<C::GasUnit>,

/// A mapping of addresses to tokens in the bank.
#[state]
pub(crate) tokens: sov_state::StateMap<C::Address, Token<C>>,
}
```

### Public Functions: The Module-to-Module Interface

The first interface that modules expose is defined by the public methods from the rollup's `impl`. These methods are
Expand Down
30 changes: 29 additions & 1 deletion module-system/module-implementations/sov-bank/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod utils;
/// Specifies the call methods using in that module.
pub use call::CallMessage;
use serde::{Deserialize, Serialize};
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 Down Expand Up @@ -41,6 +41,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 @@ -52,6 +71,10 @@ pub struct Bank<C: sov_modules_api::Context> {
#[address]
pub(crate) address: C::Address,

/// The gas configuration of the sov-bank module.
#[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 @@ -82,6 +105,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 @@ -95,22 +119,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
Loading

0 comments on commit 25ce0c1

Please sign in to comment.