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

Fix multi-argument contract calls #169

Merged
merged 2 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
40 changes: 40 additions & 0 deletions fuels-abigen-macro/tests/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,3 +1342,43 @@ async fn test_amount_and_asset_forwarding() {
&AssetId::from(NATIVE_ASSET_ID)
);
}

#[tokio::test]
async fn test_multiple_args() {
let mut rng = StdRng::seed_from_u64(2322u64);

abigen!(
MyContract,
"fuels-abigen-macro/tests/test_projects/contract_test/out/debug/contract_test-abi.json"
);

// Build the contract
let salt: [u8; 32] = rng.gen();
let salt = Salt::from(salt);

let compiled = Contract::load_sway_contract(
"tests/test_projects/contract_test/out/debug/contract_test.bin",
salt,
)
.unwrap();

let (provider, wallet) = setup_test_provider_and_wallet().await;

let id = Contract::deploy(&compiled, &provider, &wallet, TxParameters::default())
.await
.unwrap();

let instance = MyContract::new(id.to_string(), provider.clone(), wallet.clone());

// Make sure we can call the contract with multiple arguments
let response = instance.get(5, 6).call().await.unwrap();

assert_eq!(response.value, 5);

let t = MyType { x: 5, y: 6 };
let response = instance.get_alt(t).call().await.unwrap();
assert_eq!(response.value, 5);

let response = instance.get_single(5).call().await.unwrap();
assert_eq!(response.value, 5);
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,75 @@
"components": null
}
]
},
{
"type": "function",
"inputs": [
{
"name": "x",
"type": "u64",
"components": null
},
{
"name": "y",
"type": "u64",
"components": null
}
],
"name": "get",
"outputs": [
{
"name": "",
"type": "u64",
"components": null
}
]
},
{
"type": "function",
"inputs": [
{
"name": "t",
"type": "struct MyType",
"components": [
{
"name": "x",
"type": "u64",
"components": null
},
{
"name": "y",
"type": "u64",
"components": null
}
]
}
],
"name": "get_alt",
"outputs": [
{
"name": "",
"type": "u64",
"components": null
}
]
},
{
"type": "function",
"inputs": [
{
"name": "x",
"type": "u64",
"components": null
}
],
"name": "get_single",
"outputs": [
{
"name": "",
"type": "u64",
"components": null
}
]
}
]
Binary file not shown.
50 changes: 35 additions & 15 deletions fuels-abigen-macro/tests/test_projects/contract_test/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,45 @@ use std::*;
use core::*;
use std::storage::*;

struct MyType {
x: u64,
y: u64,
}

abi TestContract {
fn initialize_counter(value: u64) -> u64;
fn increment_counter(value: u64) -> u64;
fn get_counter() -> u64;
fn initialize_counter(value: u64) -> u64;
fn increment_counter(value: u64) -> u64;
fn get_counter() -> u64;
fn get(x: u64, y: u64) -> u64;
fn get_alt(x: MyType) -> u64;
fn get_single(x: u64) -> u64;
}

const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000;

impl TestContract for Contract {
fn initialize_counter(value: u64) -> u64 {
store(COUNTER_KEY, value);
value
}
fn increment_counter(value: u64) -> u64 {
let new_value = get::<u64>(COUNTER_KEY) + value;
store(COUNTER_KEY, new_value);
new_value
}
fn get_counter() -> u64 {
get::<u64>(COUNTER_KEY)
}
fn initialize_counter(value: u64) -> u64 {
store(COUNTER_KEY, value);
value
}
fn increment_counter(value: u64) -> u64 {
let new_value = get::<u64>(COUNTER_KEY) + value;
store(COUNTER_KEY, new_value);
new_value
}
fn get_counter() -> u64 {
get::<u64>(COUNTER_KEY)
}

fn get(x: u64, y: u64) -> u64 {
x
}

fn get_alt(t: MyType) -> u64 {
t.x
}

fn get_single(x: u64) -> u64 {
x
}
}
28 changes: 20 additions & 8 deletions fuels-contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Contract {
tx_parameters: TxParameters,
call_parameters: CallParameters,
maturity: Word,
custom_inputs: bool,
compute_calldata_offset: bool,
external_contracts: Option<Vec<ContractId>>,
wallet: LocalWallet,
) -> Result<Vec<Receipt>, Error> {
Expand Down Expand Up @@ -136,10 +136,11 @@ impl Contract {
script_data.extend(e)
}

// If the method call takes custom inputs, such as structs or enums, we need to calculate
// the `call_data_offset`, which points to where the data for the custom types start in the
// If the method call takes custom inputs or has more than
// one argument, we need to calculate the `call_data_offset`,
// which points to where the data for the custom types start in the
// transaction. If it doesn't take any custom inputs, this isn't necessary.
if custom_inputs {
if compute_calldata_offset {
// Offset of the script data relative to the call data
let call_data_offset = script_data_offset as usize + ContractId::LEN + 2 * WORD_SIZE;
let call_data_offset = call_data_offset as Word;
Expand Down Expand Up @@ -262,7 +263,7 @@ impl Contract {
let tx_parameters = TxParameters::default();
let call_parameters = CallParameters::default();

let custom_inputs = args.iter().any(|t| matches!(t, Token::Struct(_)));
let compute_calldata_offset = Contract::should_compute_call_data_offset(args);

let maturity = 0;
Ok(ContractCall {
Expand All @@ -275,12 +276,23 @@ impl Contract {
fuel_client: provider.client.clone(),
datatype: PhantomData,
output_params: output_params.to_vec(),
custom_inputs,
compute_calldata_offset,
external_contracts: None,
wallet: wallet.clone(),
})
}

// Returns true if the method call takes custom inputs or has more than one argument. This is used to determine whether we need to compute the `call_data_offset`.
fn should_compute_call_data_offset(args: &[Token]) -> bool {
match args
.iter()
.any(|t| matches!(t, Token::Struct(_) | Token::Enum(_)))
{
true => true,
false => args.len() > 1,
}
}

/// Deploys a compiled contract to a running node
/// To deploy a contract, you need a wallet with enough assets to pay for deployment. This
/// wallet will also receive the change.
Expand Down Expand Up @@ -362,7 +374,7 @@ pub struct ContractCall<D> {
pub maturity: u64,
pub datatype: PhantomData<D>,
pub output_params: Vec<ParamType>,
pub custom_inputs: bool,
pub compute_calldata_offset: bool,
pub wallet: LocalWallet,
external_contracts: Option<Vec<ContractId>>,
}
Expand Down Expand Up @@ -414,7 +426,7 @@ where
self.tx_parameters,
self.call_parameters,
self.maturity,
self.custom_inputs,
self.compute_calldata_offset,
self.external_contracts,
self.wallet,
)
Expand Down