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

Enable amount/asset forwarding in tx params #167

Merged
merged 3 commits into from
Mar 24, 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
4 changes: 2 additions & 2 deletions Cargo.lock

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

43 changes: 24 additions & 19 deletions fuels-abigen-macro/tests/harness.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use fuel_tx::Salt;
use fuel_tx::{AssetId, Receipt, Salt};
use fuels_abigen_macro::abigen;
use fuels_contract::contract::Contract;
use fuels_contract::errors::Error;
use fuels_contract::parameters::TxParameters;
use fuels_contract::parameters::{CallParameters, TxParameters};
use fuels_core::constants::NATIVE_ASSET_ID;
use fuels_core::Token;
use fuels_signers::util::test_helpers::{
setup_address_and_coins, setup_test_provider, setup_test_provider_and_wallet,
Expand Down Expand Up @@ -1278,7 +1279,7 @@ async fn test_gas_errors() {
.await
.expect_err("should error");

assert_eq!("Contract call error: Response errors; unexpected block execution error InsufficientGas { provided: 1, required: 100000 }", result.to_string());
assert_eq!("Contract call error: Response errors; unexpected block execution error InsufficientGas { provided: 100, required: 100000 }", result.to_string());

// Test for running out of gas. Gas price as `None` will be 0.
// Gas limit will be 100, this call will use more than 100 gas.
Expand All @@ -1293,7 +1294,7 @@ async fn test_gas_errors() {
}

#[tokio::test]
async fn token_ops() {
async fn test_amount_and_asset_forwarding() {
abigen!(
TestFuelCoinContract,
"fuels-abigen-macro/tests/test_projects/token-ops/out/debug/token-ops-abi.json"
Expand All @@ -1314,26 +1315,30 @@ async fn token_ops() {

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

let target = testfuelcoincontract_mod::ContractId { value: id.into() };
let asset_id = testfuelcoincontract_mod::ContractId { value: id.into() };
// Forward 1 coin of native asset_id
let tx_params = TxParameters::new(None, Some(1_000_000), None);
let call_params = CallParameters::new(Some(1), None);

let mut balance_result = instance
.get_balance(target.value, asset_id.clone())
let response = instance
.get_msg_amount()
.tx_params(tx_params)
.call_params(call_params)
.call()
.await
.unwrap();
assert_eq!(balance_result.value, 0);

instance.mint_coins(11).call().await.unwrap();
assert_eq!(response.value, 1);

balance_result = instance
.get_balance(target.value, asset_id)
.call()
.await
.unwrap();
assert_eq!(balance_result.value, 11);
let call_response = response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::Call { .. }));

// This is just a setup for a future work on forwarding amounts of
// a given asset_id to a contract call.
let _msg_amount = instance.get_msg_amount().call().await.unwrap();
assert!(call_response.is_some());

assert_eq!(call_response.unwrap().amount().unwrap(), 1);
assert_eq!(
call_response.unwrap().asset_id().unwrap(),
&AssetId::from(NATIVE_ASSET_ID)
);
}
4 changes: 2 additions & 2 deletions fuels-abigen-macro/tests/test_projects/token-ops/Forc.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ dependencies = []

[[package]]
name = 'std'
source = 'git+http://github.com/FuelLabs/sway-lib-std?reference=master#6a8f5bf588df5d0679fb834f05c900f2e54de426'
source = 'git+http://github.com/FuelLabs/sway-lib-std?reference=master#7b973a638d5220228be616f1f89a249846001549'
dependencies = ['core git+https://github.com/FuelLabs/sway-lib-core?reference=master#30274cf817c1848e28f984c2e8703eb25e7a3a44']

[[package]]
name = 'token-ops'
dependencies = [
'core git+http://github.com/FuelLabs/sway-lib-core?reference=master#30274cf817c1848e28f984c2e8703eb25e7a3a44',
'std git+http://github.com/FuelLabs/sway-lib-std?reference=master#6a8f5bf588df5d0679fb834f05c900f2e54de426',
'std git+http://github.com/FuelLabs/sway-lib-std?reference=master#7b973a638d5220228be616f1f89a249846001549',
]
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,14 @@
"inputs": [
{
"name": "target",
"type": "b256",
"components": null
"type": "struct ContractId",
"components": [
{
"name": "value",
"type": "b256",
"components": null
}
]
},
{
"name": "asset_id",
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ abi TestFuelCoin {
fn burn_coins(burn_amount: u64);
fn force_transfer_coins(coins: u64, asset_id: ContractId, target: ContractId);
fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address);
fn get_balance(target: b256, asset_id: ContractId) -> u64;
fn get_balance(target: ContractId, asset_id: ContractId) -> u64;
fn get_msg_amount() -> u64;
}

Expand All @@ -28,7 +28,7 @@ impl TestFuelCoin for Contract {
transfer_to_output(coins, asset_id, recipient);
}

fn get_balance(target: b256, asset_id: ContractId) -> u64 {
fn get_balance(target: ContractId, asset_id: ContractId) -> u64 {
balance_of_contract(target, asset_id)
}

Expand Down
67 changes: 46 additions & 21 deletions fuels-contract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::abi_decoder::ABIDecoder;
use crate::abi_encoder::ABIEncoder;
use crate::errors::Error;
use crate::parameters::TxParameters;
use crate::parameters::{CallParameters, TxParameters};
use crate::script::Script;
use anyhow::Result;
use fuel_asm::Opcode;
Expand Down Expand Up @@ -73,49 +73,61 @@ impl Contract {
encoded_args: Option<Vec<u8>>,
fuel_client: &FuelClient,
tx_parameters: TxParameters,
call_parameters: CallParameters,
maturity: Word,
custom_inputs: bool,
external_contracts: Option<Vec<ContractId>>,
wallet: LocalWallet,
) -> Result<Vec<Receipt>, Error> {
// Based on the defined script length, we set the appropriate data offset.
let script_len = 16;
// The script len is defined by the 6 Opcodes in the `script`, which is `24`
// plus `asset_id_offset`, which is `32`, totalling 56.
let script_len = 56;
let script_data_offset = VM_TX_MEMORY + Transaction::script_offset() + script_len;
let script_data_offset = script_data_offset as Immediate12;

// The offset that locates the asset ID in the script data.
// It goes from the beginning of `script_data` to `32`.
let asset_id_offset = 32;

// Script to call the contract. The offset that points to the `script_data` is loaded at the
// register `0x10`. Note that we're picking `0x10` simply because it could be any
// register `0x12`. Note that we're picking `0x12` simply because it could be any
// non-reserved register. Then, we use the Opcode to call a contract: `CALL` pointing at the
// register that we loaded the `script_data` at.

// The `iter().collect()` does the Opcode->u8 conversion
#[allow(clippy::iter_cloned_collect)]
let script = vec![
Opcode::ADDI(0x10, REG_ZERO, script_data_offset),
// @todo currently there's no way to forward an amount.
// This would be done by programmatically changing
// $rB (`REG_ZERO` right now) to actually point to an
// amount. This would forward the amount in $rB using
// $rC as the asset_id.
Opcode::CALL(0x10, REG_ZERO, 0x10, REG_CGAS),
// Setting `0x10` to point to the 32-byte asset ID of the amount to forward.
Opcode::ADDI(0x10, REG_ZERO, asset_id_offset),
// Setting `0x11` to hold the amount of of coins to forward.
Opcode::ADDI(0x11, REG_ZERO, call_parameters.amount as Immediate12),
// Setting `0x12` to the `script_data`, defined down below but
// we already know its length.
Opcode::ADDI(0x12, REG_ZERO, script_data_offset),
Opcode::CALL(0x12, 0x11, 0x10, REG_CGAS),
Opcode::RET(REG_RET),
Opcode::NOOP,
]
.iter()
.copied()
.collect::<Vec<u8>>();

assert_eq!(script.len(), script_len, "Script length *must* be 16");
assert_eq!(
script.len(),
script_len - asset_id_offset as usize,
"Script length *must* be 24"
);

// `script_data` consists of:
// 1. Contract ID (ContractID::LEN);
// 2. Function selector (1 * WORD_SIZE);
// 3. Calldata offset, if it has structs as input,
// 1. Asset ID to be forwarded
// 2. Contract ID (ContractID::LEN);
// 3. Function selector (1 * WORD_SIZE);
// 4. Calldata offset, if it has structs as input,
// computed as `script_data_offset` + ContractId::LEN
// + 2 * WORD_SIZE;
// 4. Encoded arguments.
// 5. Encoded arguments.
let mut script_data: Vec<u8> = vec![];

script_data.extend(call_parameters.asset_id.to_vec());

// Insert contract_id
script_data.extend(contract_id.as_ref());

Expand Down Expand Up @@ -247,15 +259,17 @@ impl Contract {
let encoded_args = encoder.encode(args).unwrap();
let encoded_selector = signature;

let params = TxParameters::default();
let tx_parameters = TxParameters::default();
let call_parameters = CallParameters::default();

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

let maturity = 0;
Ok(ContractCall {
contract_id,
encoded_args,
tx_parameters: params,
tx_parameters,
call_parameters,
maturity,
encoded_selector,
fuel_client: provider.client.clone(),
Expand Down Expand Up @@ -344,6 +358,7 @@ pub struct ContractCall<D> {
pub encoded_selector: Selector,
pub contract_id: ContractId,
pub tx_parameters: TxParameters,
pub call_parameters: CallParameters,
pub maturity: u64,
pub datatype: PhantomData<D>,
pub output_params: Vec<ParamType>,
Expand All @@ -366,7 +381,7 @@ where
self
}

/// Sets the parameters for a given contract call.
/// Sets the transaction parameters for a given transaction.
/// Note that this is a builder method, i.e. use it as a chain:
/// let params = TxParameters { gas_price: 100, gas_limit: 1000000, byte_price: 100 };
/// `my_contract_instance.my_method(...).tx_params(params).call()`.
Expand All @@ -375,6 +390,15 @@ where
self
}

/// Sets the call parameters for a given contract call.
/// Note that this is a builder method, i.e. use it as a chain:
/// let params = CallParameters { amount: 1, asset_id: NATIVE_ASSET_ID };
/// `my_contract_instance.my_method(...).call_params(params).call()`.
pub fn call_params(mut self, params: CallParameters) -> Self {
self.call_parameters = params;
self
}

/// Call a contract's method. Return a Result<CallResponse, Error>.
/// The CallResponse structs contains the method's value in its `value`
/// field as an actual typed value `D` (if your method returns `bool`, it will
Expand All @@ -388,6 +412,7 @@ where
Some(self.encoded_args),
&self.fuel_client,
self.tx_parameters,
self.call_parameters,
self.maturity,
self.custom_inputs,
self.external_contracts,
Expand Down
29 changes: 28 additions & 1 deletion fuels-contract/src/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use fuels_core::constants::{DEFAULT_BYTE_PRICE, DEFAULT_GAS_LIMIT, DEFAULT_GAS_PRICE};
use fuel_tx::AssetId;
use fuels_core::constants::{
DEFAULT_BYTE_PRICE, DEFAULT_GAS_LIMIT, DEFAULT_GAS_PRICE, NATIVE_ASSET_ID,
};

#[derive(Debug)]
pub struct TxParameters {
Expand All @@ -7,6 +10,30 @@ pub struct TxParameters {
pub byte_price: u64,
}

#[derive(Debug)]
pub struct CallParameters {
pub amount: u64,
pub asset_id: AssetId,
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}

impl CallParameters {
pub fn new(amount: Option<u64>, asset_id: Option<AssetId>) -> Self {
Self {
amount: amount.unwrap_or(0),
asset_id: asset_id.unwrap_or_else(|| NATIVE_ASSET_ID.into()),
}
}
}

impl Default for CallParameters {
fn default() -> Self {
Self {
amount: 0,
asset_id: NATIVE_ASSET_ID.into(),
}
}
}

impl Default for TxParameters {
fn default() -> Self {
Self {
Expand Down
1 change: 1 addition & 0 deletions fuels-core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub const WORD_SIZE: usize = core::mem::size_of::<Word>();
// This constant is used to determine the amount in the 1 UTXO
// when initializing wallets for now.
pub const DEFAULT_COIN_AMOUNT: u64 = 1;
pub const DEFAULT_INITIAL_BALANCE: u64 = 100;

// This constant is the bytes representation of the asset ID of
// Ethereum right now, the "native" token used for gas fees.
Expand Down
4 changes: 2 additions & 2 deletions fuels-signers/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ pub mod test_helpers {
use fuel_gql_client::client::FuelClient;
use fuel_tx::{Address, Bytes32, Bytes64, UtxoId};
use fuel_vm::prelude::Storage;
use fuels_core::constants::DEFAULT_COIN_AMOUNT;
use fuels_core::constants::DEFAULT_INITIAL_BALANCE;
use rand::{Fill, Rng};
use secp256k1::{PublicKey, Secp256k1, SecretKey};
use std::net::SocketAddr;

pub async fn setup_test_provider_and_wallet() -> (Provider, LocalWallet) {
// We build only 1 coin with amount TEST_COIN_AMOUNT, empirically determined to be
// sufficient right now
let (pk, coins) = setup_address_and_coins(1, DEFAULT_COIN_AMOUNT);
let (pk, coins) = setup_address_and_coins(1, DEFAULT_INITIAL_BALANCE);
// Setup a provider and node with the given coins
let (provider, _) = setup_test_provider(coins).await;

Expand Down