Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/stellar/rs-soroban-env into…
Browse files Browse the repository at this point in the history
… stack-limit-test
  • Loading branch information
jayz22 committed Jul 14, 2023
2 parents 268890f + 33a840b commit c17e498
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 114 deletions.
236 changes: 131 additions & 105 deletions soroban-env-host/src/auth.rs

Large diffs are not rendered by default.

73 changes: 68 additions & 5 deletions soroban-env-host/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ pub struct TransactionResources {

/// Fee-related network configuration.
///
/// This should be normally loaded from the ledger.
/// This should be normally loaded from the ledger, with exception of the
/// `fee_per_write_1kb`, that has to be computed via `compute_write_fee_per_1kb`
/// function.
pub struct FeeConfiguration {
/// Fee per `INSTRUCTIONS_INCREMENT=10000` instructions.
pub fee_per_instruction_increment: i64,
Expand All @@ -41,7 +43,8 @@ pub struct FeeConfiguration {
pub fee_per_write_entry: i64,
/// Fee per 1KB read from ledger.
pub fee_per_read_1kb: i64,
/// Fee per 1KB written to ledger.
/// Fee per 1KB written to ledger. This has to be computed via
/// `compute_write_fee_per_1kb`.
pub fee_per_write_1kb: i64,
/// Fee per 1KB written to history (the history write size is based on
/// transaction size and `TX_BASE_RESULT_SIZE`).
Expand All @@ -53,6 +56,22 @@ pub struct FeeConfiguration {
pub fee_per_propagate_1kb: i64,
}

/// Network configuration used to determine the ledger write fee.
///
/// This should be normally loaded from the ledger.
pub struct WriteFeeConfiguration {
// Write fee grows linearly until bucket list reaches this size.
pub bucket_list_target_size_bytes: i64,
// Fee per 1KB write when the bucket list is empty.
pub write_fee_1kb_bucket_list_low: i64,
// Fee per 1KB write when the bucket list has reached
// `bucket_list_target_size_bytes`.
pub write_fee_1kb_bucket_list_high: i64,
// Write fee multiplier for any additional data past the first
// `bucket_list_target_size_bytes`.
pub bucket_list_write_fee_growth_factor: u32,
}

/// Change in a single ledger entry with parameters relevant for rent fee
/// computations.
///
Expand All @@ -75,10 +94,13 @@ pub struct LedgerEntryRentChange {

/// Rent fee-related network configuration.
///
/// This should be normally loaded from the ledger.
/// This should be normally loaded from the ledger, with exception of the
/// `fee_per_write_1kb`, that has to be computed via `compute_write_fee_per_1kb`
/// function.
pub struct RentFeeConfiguration {
/// Fee per 1KB written to ledger.
/// This is the same field as in `FeeConfiguration`.
/// This is the same field as in `FeeConfiguration` and it has to be
/// computed via `compute_write_fee_per_1kb`.
pub fee_per_write_1kb: i64,
/// Denominator for the total rent fee for persistent storage.
///
Expand All @@ -96,7 +118,8 @@ pub struct RentFeeConfiguration {
/// Computes the resource fee for a transaction based on the resource
/// consumption and the fee-related network configuration.
///
/// This can handle unsantized user inputs.
/// This can handle unsantized user inputs for `tx_resources`, but expects
/// sane configuration.
///
/// Returns a pair of `(non_refundable_fee, refundable_fee)` that represent
/// non-refundable and refundable resource fee components respectively.
Expand Down Expand Up @@ -161,6 +184,46 @@ pub fn compute_transaction_resource_fee(
(non_refundable_fee, refundable_fee)
}

/// Computes the effective write fee per 1 KB of data written to ledger.
///
/// The computed fee should be used in fee configuration for
/// `compute_transaction_resource_fee` function.
///
/// This depends only on the current ledger (more specifically, bucket list)
/// size.
pub fn compute_write_fee_per_1kb(
bucket_list_size_bytes: i64,
fee_config: &WriteFeeConfiguration,
) -> i64 {
let fee_rate_multiplier =
fee_config.write_fee_1kb_bucket_list_high - fee_config.write_fee_1kb_bucket_list_low;
let bucket_list_size_before_reaching_target =
bucket_list_size_bytes.min(fee_config.bucket_list_target_size_bytes);
// Convert multipliers to i128 to make sure we can handle large bucket list
// sizes.
let mut write_fee_per_1kb = num_integer::div_ceil(
(fee_rate_multiplier as i128) * (bucket_list_size_before_reaching_target as i128),
fee_config.bucket_list_target_size_bytes as i128,
)
// The fee should be way less than i64::MAX, we do the truncation just in
// case.
.min(i64::MAX as i128) as i64;
write_fee_per_1kb = write_fee_per_1kb.saturating_add(fee_config.write_fee_1kb_bucket_list_low);
if bucket_list_size_bytes > fee_config.bucket_list_target_size_bytes {
let bucket_list_size_after_reaching_target =
bucket_list_size_bytes - fee_config.bucket_list_target_size_bytes;
let post_target_fee = num_integer::div_ceil(
(fee_rate_multiplier as i128)
* (bucket_list_size_after_reaching_target as i128)
* (fee_config.bucket_list_write_fee_growth_factor as i128),
fee_config.bucket_list_target_size_bytes as i128,
)
.min(i64::MAX as i128) as i64;
write_fee_per_1kb = write_fee_per_1kb.saturating_add(post_target_fee);
}
write_fee_per_1kb
}

/// Computes the total rent-related fee for the provided ledger entry changes.
///
/// The rent-related fees consist of the fees for rent bumps and fees for
Expand Down
13 changes: 12 additions & 1 deletion soroban-env-host/src/native_contract/token/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use soroban_env_common::xdr::{
};
use soroban_env_common::{Env, StorageType, TryIntoVal};

use super::storage_types::BalanceValue;
use super::storage_types::{BalanceValue, BALANCE_BUMP_AMOUNT};

/// This module handles all balance and authorization related logic for both
/// Accounts and non-Accounts. For Accounts, a trustline is expected (unless this
Expand All @@ -34,6 +34,11 @@ pub fn read_balance(e: &Host, addr: Address) -> Result<i128, HostError> {
if let Some(raw_balance) =
StorageUtils::try_get(e, key.try_into_val(e)?, StorageType::Persistent)?
{
e.bump_contract_data(
key.try_into_val(e)?,
StorageType::Persistent,
BALANCE_BUMP_AMOUNT.into(),
)?;
let balance: BalanceValue = raw_balance.try_into_val(e)?;
Ok(balance.amount)
} else {
Expand All @@ -59,6 +64,12 @@ fn write_balance(e: &Host, addr: Address, balance: BalanceValue) -> Result<(), H
StorageType::Persistent,
().into(),
)?;

e.bump_contract_data(
key.try_into_val(e)?,
StorageType::Persistent,
BALANCE_BUMP_AMOUNT.into(),
)?;
Ok(())
}

Expand Down
40 changes: 39 additions & 1 deletion soroban-env-host/src/native_contract/token/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::native_contract::token::public_types::AssetInfo;
use crate::{err, HostError};

use soroban_env_common::xdr::Asset;
use soroban_env_common::{ConversionError, EnvBase, TryFromVal, TryIntoVal};
use soroban_env_common::{ConversionError, Env, EnvBase, TryFromVal, TryIntoVal};
use soroban_native_sdk_macros::contractimpl;

use super::admin::{read_administrator, write_administrator};
Expand All @@ -21,6 +21,7 @@ use super::balance::{
};
use super::metadata::{read_name, read_symbol, set_metadata, DECIMAL};
use super::public_types::{AlphaNum12AssetInfo, AlphaNum4AssetInfo};
use super::storage_types::INSTANCE_BUMP_AMOUNT;

pub trait TokenTrait {
/// init_asset can create a contract for a wrapped classic asset
Expand Down Expand Up @@ -71,6 +72,8 @@ pub trait TokenTrait {

fn set_admin(e: &Host, new_admin: Address) -> Result<(), HostError>;

fn admin(e: &Host) -> Result<Address, HostError>;

fn decimals(e: &Host) -> Result<u32, HostError>;

fn name(e: &Host) -> Result<String, HostError>;
Expand Down Expand Up @@ -178,6 +181,7 @@ impl TokenTrait for Token {
}

fn allowance(e: &Host, from: Address, spender: Address) -> Result<i128, HostError> {
e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;
read_allowance(e, from, spender)
}

Expand All @@ -191,29 +195,38 @@ impl TokenTrait for Token {
) -> Result<(), HostError> {
check_nonnegative_amount(e, amount)?;
from.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

write_allowance(e, from.clone(), spender.clone(), amount, expiration_ledger)?;
event::approve(e, from, spender, amount, expiration_ledger)?;
Ok(())
}

// Metering: covered by components
fn balance(e: &Host, addr: Address) -> Result<i128, HostError> {
e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;
read_balance(e, addr)
}

fn spendable_balance(e: &Host, addr: Address) -> Result<i128, HostError> {
e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;
get_spendable_balance(e, addr)
}

// Metering: covered by components
fn authorized(e: &Host, addr: Address) -> Result<bool, HostError> {
e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;
is_authorized(e, addr)
}

// Metering: covered by components
fn transfer(e: &Host, from: Address, to: Address, amount: i128) -> Result<(), HostError> {
check_nonnegative_amount(e, amount)?;
from.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

spend_balance(e, from.clone(), amount)?;
receive_balance(e, to.clone(), amount)?;
event::transfer(e, from, to, amount)?;
Expand All @@ -230,6 +243,9 @@ impl TokenTrait for Token {
) -> Result<(), HostError> {
check_nonnegative_amount(e, amount)?;
spender.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

spend_allowance(e, from.clone(), spender, amount)?;
spend_balance(e, from.clone(), amount)?;
receive_balance(e, to.clone(), amount)?;
Expand All @@ -242,6 +258,9 @@ impl TokenTrait for Token {
check_nonnegative_amount(e, amount)?;
check_non_native(e)?;
from.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

spend_balance(e, from.clone(), amount)?;
event::burn(e, from, amount)?;
Ok(())
Expand All @@ -252,6 +271,9 @@ impl TokenTrait for Token {
check_nonnegative_amount(e, amount)?;
check_non_native(e)?;
spender.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

spend_allowance(e, from.clone(), spender, amount)?;
spend_balance(e, from.clone(), amount)?;
event::burn(e, from, amount)?;
Expand All @@ -264,6 +286,9 @@ impl TokenTrait for Token {
check_clawbackable(e, from.clone())?;
let admin = read_administrator(e)?;
admin.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

spend_balance_no_authorization_check(e, from.clone(), amount)?;
event::clawback(e, admin, from, amount)?;
Ok(())
Expand All @@ -273,6 +298,9 @@ impl TokenTrait for Token {
fn set_authorized(e: &Host, addr: Address, authorize: bool) -> Result<(), HostError> {
let admin = read_administrator(e)?;
admin.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

write_authorization(e, addr.clone(), authorize)?;
event::set_authorized(e, admin, addr, authorize)?;
Ok(())
Expand All @@ -283,6 +311,9 @@ impl TokenTrait for Token {
check_nonnegative_amount(e, amount)?;
let admin = read_administrator(e)?;
admin.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

receive_balance(e, to.clone(), amount)?;
event::mint(e, admin, to, amount)?;
Ok(())
Expand All @@ -292,11 +323,18 @@ impl TokenTrait for Token {
fn set_admin(e: &Host, new_admin: Address) -> Result<(), HostError> {
let admin = read_administrator(e)?;
admin.require_auth()?;

e.bump_current_contract_instance_and_code(INSTANCE_BUMP_AMOUNT.into())?;

write_administrator(e, new_admin.clone())?;
event::set_admin(e, admin, new_admin)?;
Ok(())
}

fn admin(e: &Host) -> Result<Address, HostError> {
read_administrator(e)
}

fn decimals(_e: &Host) -> Result<u32, HostError> {
// no need to load metadata since this is fixed for all SAC tokens
Ok(DECIMAL)
Expand Down
3 changes: 3 additions & 0 deletions soroban-env-host/src/native_contract/token/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use crate::native_contract::base_types::Address;
use soroban_env_common::TryIntoVal;
use soroban_native_sdk_macros::contracttype;

pub(crate) const INSTANCE_BUMP_AMOUNT: u32 = 34560; // 2 days
pub(crate) const BALANCE_BUMP_AMOUNT: u32 = 518400; // 30 days

#[contracttype]
pub struct AllowanceDataKey {
pub from: Address,
Expand Down
10 changes: 10 additions & 0 deletions soroban-env-host/src/native_contract/token/test_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,16 @@ impl<'a> TestToken<'a> {
self.call_with_single_signer(admin, "set_admin", host_vec![self.host, new_admin])
}

pub(crate) fn admin(&self) -> Result<Address, HostError> {
self.host
.call(
self.address.clone().into(),
Symbol::try_from_val(self.host, &"admin")?,
host_vec![self.host].into(),
)?
.try_into_val(self.host)
}

pub(crate) fn decimals(&self) -> Result<u32, HostError> {
Ok(self
.host
Expand Down
30 changes: 30 additions & 0 deletions soroban-env-host/src/test/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,36 @@ fn test_single_authorized_call_tree() {
);
}

#[test]
fn test_disjoint_tree_not_allowed() {
let mut test = AuthTest::setup(1, 2);
let setup = SetupNode::new(
&test.contracts[0],
vec![true],
vec![SetupNode::new(&test.contracts[1], vec![true], vec![])],
);
// Correct call
test.tree_test_enforcing(
&setup,
vec![vec![SignNode::tree_fn(
&test.contracts[0],
vec![SignNode::tree_fn(&test.contracts[1], vec![])],
)]],
true,
);
test.verify_nonces_consumed(vec![1]);

// Incorrect call - tree has been split into two separate nodes.
test.tree_test_enforcing(
&setup,
vec![vec![
SignNode::tree_fn(&test.contracts[0], vec![]),
SignNode::tree_fn(&test.contracts[1], vec![]),
]],
false,
);
}

#[test]
fn test_two_authorized_trees() {
let mut test = AuthTest::setup(1, 5);
Expand Down
11 changes: 11 additions & 0 deletions soroban-env-host/src/test/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ fn test_native_token_smart_roundtrip() {

// Also can't set a new admin (and there is no admin in the first place).
assert!(token.set_admin(&user, user.address(&test.host)).is_err());
assert!(token.admin().is_err());

assert_eq!(test.get_native_balance(&account_id), 100_000_000);
assert_eq!(
Expand Down Expand Up @@ -1166,11 +1167,21 @@ fn test_set_admin() {
test.create_default_account(&user);
test.create_default_trustline(&user);

assert_eq!(
token.admin().unwrap().to_sc_address().unwrap(),
admin.address(&test.host).to_sc_address().unwrap()
);

// Give admin rights to the new admin.
token
.set_admin(&admin, new_admin.address(&test.host))
.unwrap();

assert_eq!(
token.admin().unwrap().to_sc_address().unwrap(),
new_admin.address(&test.host).to_sc_address().unwrap()
);

// Make sure admin functions are unavailable to the old admin.
assert_eq!(
token
Expand Down
Loading

0 comments on commit c17e498

Please sign in to comment.