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

PassAll gas option for program to program calls #1417

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions x/contracts/examples/automated-market-maker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::cmp;
use token::Units;
use wasmlanche::{
public, state_schema, Address, Context, ContractId, ExternalCallArgs, ExternalCallContext, Gas,
public, state_schema, Address, Context, ContractId, ExternalCallArgs, ExternalCallContext,
};

mod math;
Expand All @@ -17,13 +17,13 @@ state_schema! {
LiquidityToken => Address,
}

const MAX_GAS: Gas = 10_000_000;
const MAX_GAS: u64 = 10_000_000;
const ZERO: u64 = 0;

fn call_args_from_address(address: Address) -> ExternalCallArgs {
ExternalCallArgs {
contract_address: address,
max_units: MAX_GAS,
max_units: MAX_GAS.into(),
value: ZERO,
}
}
Expand Down
4 changes: 2 additions & 2 deletions x/contracts/examples/counter-external/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use wasmlanche::{public, Address, Context, ExternalCallArgs};
pub fn inc(ctx: &mut Context, contract_address: Address, of: Address) {
let args = ExternalCallArgs {
contract_address,
max_units: 1_000_000,
max_units: 1_000_000.into(),
value: 0,
};

Expand All @@ -20,7 +20,7 @@ pub fn inc(ctx: &mut Context, contract_address: Address, of: Address) {
pub fn get_value(ctx: &mut Context, contract_address: Address, of: Address) -> u64 {
let args = ExternalCallArgs {
contract_address,
max_units: 1_000_000,
max_units: 1_000_000.into(),
value: 0,
};

Expand Down
16 changes: 11 additions & 5 deletions x/contracts/runtime/import_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ func NewContractModule(r *WasmRuntime) *ImportModule {
"call_contract": {FuelCost: callContractCost, Function: Function[callContractInput, Result[RawBytes, ContractCallErrorCode]](func(callInfo *CallInfo, input callContractInput) (Result[RawBytes, ContractCallErrorCode], error) {
newInfo := *callInfo

if err := callInfo.ConsumeFuel(input.Fuel); err != nil {
return Err[RawBytes, ContractCallErrorCode](OutOfFuel), nil //nolint:nilerr
if input.Fuel == 0 {
newInfo.Fuel = callInfo.RemainingFuel()
} else {
if err := callInfo.ConsumeFuel(input.Fuel); err != nil {
return Err[RawBytes, ContractCallErrorCode](OutOfFuel), nil //nolint:nilerr
}
newInfo.Fuel = input.Fuel
}

newInfo.Actor = callInfo.Contract
newInfo.Contract = input.Contract
newInfo.FunctionName = input.FunctionName
newInfo.Params = input.Params
newInfo.Fuel = input.Fuel
newInfo.Value = input.Value

result, err := r.CallContract(
Expand All @@ -85,8 +89,10 @@ func NewContractModule(r *WasmRuntime) *ImportModule {
return Err[RawBytes, ContractCallErrorCode](ExecutionFailure), err
}

// return any remaining fuel to the calling contract
callInfo.AddFuel(newInfo.RemainingFuel())
// return any remaining fuel to the calling program
if callInfo.Fuel > 0 {
callInfo.AddFuel(newInfo.RemainingFuel())
}

return Ok[RawBytes, ContractCallErrorCode](result), nil
})},
Expand Down
9 changes: 6 additions & 3 deletions x/contracts/test/contracts/balance/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

use wasmlanche::{public, Address, Context, Gas};
use wasmlanche::{public, Address, Context};

#[public]
pub fn balance(ctx: &mut Context) -> u64 {
Expand All @@ -14,7 +14,10 @@ pub fn send_balance(ctx: &mut Context, recipient: Address) -> bool {
}

#[public]
pub fn send_via_call(ctx: &mut Context, target: Address, max_units: Gas, value: u64) -> u64 {
ctx.call_contract(target, "balance", &[], max_units, value)
pub fn send_via_call(ctx: &mut Context, target: Address, max_units: u64, value: u64) -> u64 {
ctx.call_contract_builder(target)
.with_max_units(max_units)
.with_value(value)
.call_function("balance", &[])
.unwrap()
}
4 changes: 3 additions & 1 deletion x/contracts/test/contracts/fuel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub fn get_fuel(ctx: &mut Context) -> u64 {

#[public]
pub fn out_of_fuel(ctx: &mut Context, target: Address) -> ExternalCallError {
ctx.call_contract::<u64>(target, "get_fuel", &[], 0, 0)
ctx.call_contract_builder(target)
.with_max_units(1)
.call_function::<u64>("get_fuel", &[])
.unwrap_err()
}
4 changes: 2 additions & 2 deletions x/contracts/test/contracts/return_complex_type/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

use wasmlanche::{borsh::BorshSerialize, public, Address, Context, Gas};
use wasmlanche::{borsh::BorshSerialize, public, Address, Context};

#[derive(BorshSerialize)]
#[borsh(crate = "wasmlanche::borsh")]
pub struct ComplexReturn {
account: Address,
max_units: Gas,
max_units: u64,
}

#[public]
Expand Down
48 changes: 47 additions & 1 deletion x/contracts/wasmlanche/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ impl Context {
borsh::from_slice(&bytes).expect("failed to deserialize")
}

pub fn call_contract_builder(&mut self, address: Address) -> CallContractBuilder<'_> {
CallContractBuilder::with_address(self, address)
}

#[cfg(feature = "bindings")]
#[must_use]
pub fn to_extern(&mut self, args: ExternalCallArgs) -> ExternalCallContext<'_, Self> {
Expand Down Expand Up @@ -299,7 +303,7 @@ impl Context {
function_name,
args,
value,
max_units: 0,
max_units: Gas::PassAll,
};

let contract_args = borsh::to_vec(&(CALL_FUNCTION_PREFIX, contract_args))
Expand Down Expand Up @@ -334,6 +338,48 @@ impl Context {
}
}

pub struct CallContractBuilder<'a> {
ctx: &'a mut Context,
address: Address,
max_units: Gas,
value: Option<u64>,
}

impl<'a> CallContractBuilder<'a> {
fn with_address(ctx: &'a mut Context, address: Address) -> Self {
CallContractBuilder {
ctx,
address,
max_units: Gas::PassAll,
value: None,
}
}

pub fn with_max_units(mut self, units: u64) -> Self {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if this function could only accept units that are > 0, there might be some semantic changes to do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, replicate the Gas enum type on the go side too so that when it's the Unit variant then it's only interpreted as "pass x units" even when it's 0 to avoid any runtime panic because of the conversion from a u64 to a NonZeroU64.

Copy link
Contributor Author

@iFrostizz iFrostizz Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might also want to replace the call_contract function by a function that only accepts the Address, function_name, args.

Copy link
Contributor Author

@iFrostizz iFrostizz Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep being explicit on the gas forwarding, having a function named with_max_units(u64) and with_pass_all_gas() would make sense. The default behaviour could be to use Gas::PassAll, so the with_pass_all_gas could be ignored.

self.max_units = units.into();
self
}

pub fn with_value(mut self, value: u64) -> Self {
self.value = Some(value);
self
}

pub fn call_function<T: BorshDeserialize>(
self,
function_name: &str,
args: &[u8],
) -> Result<T, ExternalCallError> {
self.ctx.call_contract(
self.address,
function_name,
args,
self.max_units,
self.value.unwrap_or_default(),
)
}
}

/// An error that is returned from call to public functions.
#[derive(Debug, Display, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
#[repr(u8)]
Expand Down
2 changes: 1 addition & 1 deletion x/contracts/wasmlanche/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ mod test_wrappers {
Self {
state: RefCell::new(hashbrown::HashMap::new()),
deploys: Cell::new(0),
fuel: u64::MAX,
fuel: u64::MAX.into(),
}
}

Expand Down
40 changes: 39 additions & 1 deletion x/contracts/wasmlanche/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,52 @@ extern crate alloc;
use alloc::boxed::Box;
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{Pod, Zeroable};
use core::num::NonZeroU64;
use core::{array, mem::size_of};

/// Byte length of an action ID.
pub const ID_LEN: usize = 32;
/// Action id.
pub type Id = [u8; ID_LEN];

/// Gas type alias.
pub type Gas = u64;
#[derive(Clone, Copy, Debug)]
pub enum Gas {
PassAll,
Units(NonZeroU64),
}

impl From<&Gas> for u64 {
fn from(value: &Gas) -> Self {
match value {
Gas::PassAll => 0,
Gas::Units(num) => num.get(),
}
}
}

impl From<u64> for Gas {
fn from(value: u64) -> Self {
match value {
0 => Gas::PassAll,
num => Gas::Units(unsafe { NonZeroU64::new_unchecked(num) }),
}
}
}

impl BorshSerialize for Gas {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
let num: u64 = self.into();
num.serialize(writer)
}
}

impl BorshDeserialize for Gas {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let num = u64::deserialize_reader(reader)?;
Ok(num.into())
}
}

/// The ID bytes of a contract.
#[derive(BorshSerialize, BorshDeserialize)]
Expand Down
Loading