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
7 changes: 2 additions & 5 deletions x/contracts/examples/automated-market-maker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@ state_schema! {
LiquidityToken => Address,
}

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

fn call_args_from_address(address: Address) -> ExternalCallArgs {
ExternalCallArgs {
contract_address: address,
max_units: MAX_GAS,
value: ZERO,
max_units: Gas::PassAll,
value: 0,
}
}

Expand Down
6 changes: 3 additions & 3 deletions x/contracts/examples/counter-external/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::{public, Address, Context, ExternalCallArgs};
use wasmlanche::{public, Address, Context, ExternalCallArgs, Gas};

#[public]
pub fn inc(ctx: &mut Context, contract_address: Address, of: Address) {
let args = ExternalCallArgs {
contract_address,
max_units: 1_000_000,
max_units: Gas::PassAll,
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: Gas::PassAll,
value: 0,
};

Expand Down
3 changes: 3 additions & 0 deletions x/contracts/runtime/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type CallInfo struct {
// the serialized parameters that will be passed to the called function
Params []byte

// true if the call passes all gas
PassAll bool

// the maximum amount of fuel allowed to be consumed by wasm for this call
Fuel uint64
Comment on lines +48 to 52
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would be interested to know if there could be a cleaner solution. Currently there is a bool and the uint64 that tells the amount of fuel passed, but since having PassAll = true and Fuel > 0 is inconsistent state, this has to be validated through the execution. Is there a better way to simulate this rust enum:

enum Gas {
    PassAll,
    Units(u64)
}


Expand Down
14 changes: 11 additions & 3 deletions x/contracts/runtime/import_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type callContractInput struct {
Contract codec.Address
FunctionName string
Params []byte
PassAll bool
Fuel uint64
Value uint64
}
Expand Down Expand Up @@ -64,15 +65,22 @@ 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.PassAll {
if input.Fuel > 0 {
return Err[RawBytes, ContractCallErrorCode](ExecutionFailure), nil
}
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 Down
9 changes: 7 additions & 2 deletions x/contracts/simulator/common/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ typedef struct {
size_t length;
} Bytes;

typedef struct {
uint8_t pass_all;
uint64_t units;
} Gas;

typedef Bytes ContractId;

// Bytes with an additional error field
Expand All @@ -40,8 +45,8 @@ typedef struct {
const char* method;
// params borsh serialized as byte vector
Bytes params;
// max allowed gas during execution
uint64_t max_gas;
// passed gas during execution
Gas gas;
} SimulatorCallContext;

// Response from calling a contract
Expand Down
25 changes: 21 additions & 4 deletions x/contracts/simulator/ffi/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ func CallContract(db *C.Mutable, ctx *C.SimulatorCallContext) C.CallContractResp
// build the db
state := simState.NewSimulatorState(unsafe.Pointer(db))
// build the call info
callInfo := createRuntimeCallInfo(state, ctx)
callInfo, err := createRuntimeCallInfo(state, ctx)
if err != nil {
return newCallContractResponse(nil, 0, fmt.Errorf("error during runtime execution: %w", err))
}
config := runtime.NewConfig()
config.SetDebugInfo(true)

Expand All @@ -56,22 +59,36 @@ func CallContract(db *C.Mutable, ctx *C.SimulatorCallContext) C.CallContractResp
return newCallContractResponse(result, fuel, nil)
}

func createRuntimeCallInfo(db state.Mutable, ctx *C.SimulatorCallContext) *runtime.CallInfo {
func createRuntimeCallInfo(db state.Mutable, ctx *C.SimulatorCallContext) (*runtime.CallInfo, error) {
paramBytes := C.GoBytes(unsafe.Pointer(ctx.params.data), C.int(ctx.params.length))
methodName := C.GoString(ctx.method)
actorBytes := C.GoBytes(unsafe.Pointer(&ctx.actor_address), codec.AddressLen) //nolint:all
contractBytes := C.GoBytes(unsafe.Pointer(&ctx.contract_address), codec.AddressLen) //nolint:all

var passAll bool
switch ctx.gas.pass_all {
case 0:
passAll = false
case 1:
passAll = true
if ctx.gas.units > 0 {
return nil, errors.New("cannot spend gas while passing all")
}
default:
return nil, errors.New("passAll should be a boolean")
}

return &runtime.CallInfo{
State: simState.NewContractStateManager(db),
Actor: codec.Address(actorBytes),
FunctionName: methodName,
Contract: codec.Address(contractBytes),
Params: paramBytes,
Fuel: uint64(ctx.max_gas),
PassAll: passAll,
Fuel: uint64(ctx.gas.units),
Height: uint64(ctx.height),
Timestamp: uint64(ctx.timestamp),
}
}, nil
}

//export CreateContract
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()
}
34 changes: 18 additions & 16 deletions x/contracts/test/contracts/call_contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// 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 simple_call(_: &mut Context) -> i64 {
0
}

#[public]
pub fn simple_call_external(ctx: &mut Context, target: Address, max_units: Gas) -> i64 {
ctx.call_contract(target, "simple_call", &[], max_units, 0)
pub fn simple_call_external(ctx: &mut Context, target: Address, max_units: u64) -> i64 {
ctx.call_contract_builder(target)
.with_max_units(max_units)
.call_function("simple_call", &[])
.unwrap()
}

Expand All @@ -20,8 +22,10 @@ pub fn actor_check(context: &mut Context) -> Address {
}

#[public]
pub fn actor_check_external(ctx: &mut Context, target: Address, max_units: Gas) -> Address {
ctx.call_contract(target, "actor_check", &[], max_units, 0)
pub fn actor_check_external(ctx: &mut Context, target: Address, max_units: u64) -> Address {
ctx.call_contract_builder(target)
.with_max_units(max_units)
.call_function("actor_check", &[])
.expect("failure")
}

Expand All @@ -34,17 +38,13 @@ pub fn call_with_param(_: &mut Context, value: i64) -> i64 {
pub fn call_with_param_external(
ctx: &mut Context,
target: Address,
max_units: Gas,
max_units: u64,
value: i64,
) -> i64 {
ctx.call_contract(
target,
"call_with_param",
&value.to_le_bytes(),
max_units,
0,
)
.unwrap()
ctx.call_contract_builder(target)
.with_max_units(max_units)
.call_function("call_with_param", &value.to_le_bytes())
.unwrap()
}

#[public]
Expand All @@ -56,7 +56,7 @@ pub fn call_with_two_params(_: &mut Context, value1: i64, value2: i64) -> i64 {
pub fn call_with_two_params_external(
ctx: &mut Context,
target: Address,
max_units: Gas,
max_units: u64,
value1: i64,
value2: i64,
) -> i64 {
Expand All @@ -65,6 +65,8 @@ pub fn call_with_two_params_external(
.into_iter()
.chain(value2.to_le_bytes())
.collect();
ctx.call_contract(target, "call_with_two_params", &args, max_units, 0)
ctx.call_contract_builder(target)
.with_max_units(max_units)
.call_function("call_with_two_params", &args)
.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(0)
.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
58 changes: 57 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,58 @@ impl Context {
}
}

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

impl<'a> CallContractBuilder<'a> {
/// Creates a new context with an address.
fn with_address(ctx: &'a mut Context, address: Address) -> Self {
CallContractBuilder {
ctx,
address,
max_units: Gas::PassAll,
value: 0,
}
}

/// Specifies the amount of units that should be passed to the context.
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
}

/// Sets the gas forwarding rule to be "pass-all". This is the default behaviour.
pub fn with_pass_all(mut self) -> Self {
self.max_units = Gas::PassAll;
self
}

/// Sets the value passed to the context. Defaults to 0.
pub fn with_value(mut self, value: u64) -> Self {
self.value = value;
self
}

/// Consumes the builder and call the specified function with the args.
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,
)
}
}

/// 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
Loading
Loading