Skip to content

Commit

Permalink
feat: deprecate tx funding call option (#1136)
Browse files Browse the repository at this point in the history
* feat: deprecate tx funding call option

* docs: alter read only docs around tx funding

* chore: changeset

* chore: linting

---------

Co-authored-by: danielbate <--global>
  • Loading branch information
danielbate committed Jul 26, 2023
1 parent d47f07a commit f5e0ded
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 54 deletions.
8 changes: 8 additions & 0 deletions .changeset/sour-emus-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"docs": patch
"@fuel-ts/docs-snippets": patch
"@fuel-ts/program": patch
"@fuel-ts/script": patch
---

Deprecate tx funding call option as all txs require a spendable input
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ describe(__filename, () => {
it('should successfully execute a read only call', async () => {
// #region read-only-call-1
const { value } = await contract.functions.echo_u8(15).get();

expect(value).toEqual(15);
// #endregion read-only-call-1
expect(value).toEqual(15);
});
});
13 changes: 2 additions & 11 deletions apps/docs/src/guide/contracts/read-only-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@ In such cases, there's no need to create an actual blockchain transaction; you o

<<< @/../../docs-snippets/src/guide/contracts/read-only-calls.test.ts#read-only-call-1{ts:line-numbers}

<!-- This section should explain why read-only calls don't require funds -->
<!-- get:example:start -->

`get()` doesn't take funding, as it is a read-only call that doesn't alter the chain state.

<!-- get:example:end -->

<!-- TODO: review dryRun sentence. Consider creating a new doc page for it as it seems to be a different subject -->

If you want to dry run a transaction call that takes funding without altering the chain state, use `dryRun()`.

## When to use `get()` vs `call()`

<!-- This section should explain when to use the get vs call methods -->
Expand All @@ -32,3 +21,5 @@ If you want to dry run a transaction call that takes funding without altering th
`call()`: Appropriate for state-changing calls; use this method when calling a function that modifies the blockchain's state, like transferring funds or updating values. Since these things involve changing the blockchain state, `call()` requires gas and transaction fees to be paid.

<!-- get_call:example:end -->

> **Note:** Although a `get()` call will not modify the blockchain's state and therefore does not require gas; both functions require a spendable input to be set on the transaction for it to be considered valid.
50 changes: 16 additions & 34 deletions packages/program/src/functions/base-invocation-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ import { InputType } from '@fuel-ts/transactions';
import { MAX_GAS_PER_TX } from '@fuel-ts/transactions/configs';

import { contractCallScript } from '../contract-call-script';
import type {
CallOptions,
ContractCall,
InvocationScopeLike,
TransactionCostOptions,
TxParams,
} from '../types';
import type { ContractCall, InvocationScopeLike, TransactionCostOptions, TxParams } from '../types';
import { assert } from '../utils';

import { InvocationCallResult, FunctionInvocationResult } from './invocation-results';
Expand Down Expand Up @@ -58,10 +52,6 @@ export class BaseInvocationScope<TReturn = any> {
return this.functionInvocationScopes.map((funcScope) => createContractCall(funcScope));
}

protected static getCallOptions(options?: CallOptions) {
return { fundTransaction: true, ...options };
}

protected updateScriptRequest() {
const calls = this.calls;
calls.forEach((c) => {
Expand Down Expand Up @@ -111,7 +101,7 @@ export class BaseInvocationScope<TReturn = any> {
return this;
}

protected async prepareTransaction(options?: CallOptions) {
protected async prepareTransaction() {
// Update request scripts before call
this.updateScriptRequest();

Expand All @@ -122,9 +112,7 @@ export class BaseInvocationScope<TReturn = any> {
// sum of all call gasLimits
this.checkGasLimitTotal();

// Add funds required on forwards and to pay gas
const opts = BaseInvocationScope.getCallOptions(options);
if (opts.fundTransaction && this.program.account) {
if (this.program.account) {
await this.fundWithRequiredCoins();
}
}
Expand All @@ -146,7 +134,7 @@ export class BaseInvocationScope<TReturn = any> {
const provider = (this.program.account?.provider || this.program.provider) as Provider;
assert(provider, 'Wallet or Provider is required!');

await this.prepareTransaction(options);
await this.prepareTransaction();
const request = transactionRequestify(this.transactionRequest);
request.gasPrice = bn(toNumber(request.gasPrice) || toNumber(options?.gasPrice || 0));
const txCost = await provider.getTransactionCost(request, options?.tolerance);
Expand Down Expand Up @@ -195,8 +183,8 @@ export class BaseInvocationScope<TReturn = any> {
* It's possible to get the transaction without adding coins, by passing `fundTransaction`
* as false.
*/
async getTransactionRequest(options?: CallOptions): Promise<TransactionRequest> {
await this.prepareTransaction(options);
async getTransactionRequest(): Promise<TransactionRequest> {
await this.prepareTransaction();
return this.transactionRequest;
}

Expand All @@ -207,10 +195,10 @@ export class BaseInvocationScope<TReturn = any> {
* It also means that invalid transactions will throw an error, and consume gas. To avoid this
* running invalid tx and consuming gas try to `simulate` first when possible.
*/
async call<T = TReturn>(options?: CallOptions): Promise<FunctionInvocationResult<T>> {
async call<T = TReturn>(): Promise<FunctionInvocationResult<T>> {
assert(this.program.account, 'Wallet is required!');

const transactionRequest = await this.getTransactionRequest(options);
const transactionRequest = await this.getTransactionRequest();
const response = await this.program.account.sendTransaction(transactionRequest);

return FunctionInvocationResult.build<T>(
Expand All @@ -228,10 +216,10 @@ export class BaseInvocationScope<TReturn = any> {
* This method is useful for validate propose to avoid spending coins on invalid TXs, also
* to estimate the amount of gas that will be required to run the transaction.
*/
async simulate<T = TReturn>(options?: CallOptions): Promise<InvocationCallResult<T>> {
async simulate<T = TReturn>(): Promise<InvocationCallResult<T>> {
assert(this.program.account, 'Wallet is required!');

const transactionRequest = await this.getTransactionRequest(options);
const transactionRequest = await this.getTransactionRequest();
const result = await this.program.account.simulateTransaction(transactionRequest);

return InvocationCallResult.build<T>(this.functionInvocationScopes, result, this.isMultiCall);
Expand All @@ -245,11 +233,11 @@ export class BaseInvocationScope<TReturn = any> {
* The UTXO validation disable in this case, enables to send invalid inputs to emulate different conditions, of a
* transaction
*/
async dryRun<T = TReturn>(options?: CallOptions): Promise<InvocationCallResult<T>> {
async dryRun<T = TReturn>(): Promise<InvocationCallResult<T>> {
const provider = (this.program.account?.provider || this.program.provider) as Provider;
assert(provider, 'Wallet or Provider is required!');

const transactionRequest = await this.getTransactionRequest(options);
const transactionRequest = await this.getTransactionRequest();
const request = transactionRequestify(transactionRequest);
const response = await provider.call(request, {
utxoValidation: false,
Expand All @@ -265,17 +253,11 @@ export class BaseInvocationScope<TReturn = any> {
}

/**
* Executes a readonly contract method call.
* Executes a readonly contract method call by dry running a transaction.
*
* Under the hood it uses the `dryRun` method but don't fund the transaction
* with coins by default, for emulating executions with forward coins use `dryRun`
* or pass the options.fundTransaction as true
*
* TODO: refactor out use of get() in place of call()
* Note: This method is the same as `dryRun` but with a more semantic name that consumers are familiar with.
*/
async get<T = TReturn>(options?: CallOptions): Promise<InvocationCallResult<T>> {
return this.dryRun<T>({
...options,
});
async get<T = TReturn>(): Promise<InvocationCallResult<T>> {
return this.dryRun<T>();
}
}
4 changes: 0 additions & 4 deletions packages/program/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export type TxParams = Partial<{
}>;
// #endregion transaction-params

export type CallOptions = Partial<{
fundTransaction: boolean;
}>;

export type CallConfig<T = unknown> = {
func: FunctionFragment;
program: AbstractProgram;
Expand Down
6 changes: 3 additions & 3 deletions packages/script/src/script-invocation-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
FunctionInvocationScope,
FunctionInvocationResult,
} from '@fuel-ts/program';
import type { CallOptions, InvocationScopeLike } from '@fuel-ts/program';
import type { InvocationScopeLike } from '@fuel-ts/program';

export class ScriptInvocationScope<
TArgs extends Array<any> = Array<any>,
Expand Down Expand Up @@ -36,10 +36,10 @@ export class ScriptInvocationScope<
/**
* Submits a script transaction to the blockchain.
*/
async call<T = TReturn>(options?: CallOptions): Promise<FunctionInvocationResult<T>> {
async call<T = TReturn>(): Promise<FunctionInvocationResult<T>> {
assert(this.program.account, 'Provider is required!');

const transactionRequest = await this.getTransactionRequest(options);
const transactionRequest = await this.getTransactionRequest();
const response = await this.program.account.sendTransaction(transactionRequest);

return FunctionInvocationResult.build<T>(
Expand Down

0 comments on commit f5e0ded

Please sign in to comment.