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

feat: add isReadOnly helper for functions #2008

Merged
merged 26 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3ac5d70
feat: add isReadOnly helper for functions
Dhaiwat10 Apr 3, 2024
c50631e
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 3, 2024
e25eea1
add testing group
Dhaiwat10 Apr 3, 2024
6d00d3a
refactor
Dhaiwat10 Apr 3, 2024
62ca5f0
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 3, 2024
d6fba1e
fix lint error
Dhaiwat10 Apr 3, 2024
8fe3e79
Merge branch 'master' into dp/function-readonly-helper
danielbate Apr 4, 2024
13545da
move assertion out of docs snippet
Dhaiwat10 Apr 4, 2024
36a88ab
improve the API
Dhaiwat10 Apr 4, 2024
44408be
move tests to a common require block
Dhaiwat10 Apr 4, 2024
aad229c
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 4, 2024
780cf28
reword docs
Dhaiwat10 Apr 5, 2024
4d5646b
add type annotation
Dhaiwat10 Apr 5, 2024
eac3cb2
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 5, 2024
8ed9f35
Merge branch 'master' into dp/function-readonly-helper
petertonysmith94 Apr 8, 2024
db37a6e
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 8, 2024
ec413f5
add a test case covering a pure function
Dhaiwat10 Apr 9, 2024
1b7ca02
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 9, 2024
ed4c54b
update docs
Dhaiwat10 Apr 9, 2024
bd4ddcc
Merge branch 'dp/function-readonly-helper' of github.com:fuellabs/fue…
Dhaiwat10 Apr 9, 2024
4006c40
Merge branch 'master' into dp/function-readonly-helper
danielbate Apr 9, 2024
f445db1
reword docs
Dhaiwat10 Apr 10, 2024
2b04478
reword docs
Dhaiwat10 Apr 10, 2024
4c761d8
add onchain to spellcheck custom wordlist
Dhaiwat10 Apr 10, 2024
540cfb1
Merge branch 'master' into dp/function-readonly-helper
Dhaiwat10 Apr 10, 2024
ec948c9
Merge branch 'master' into dp/function-readonly-helper
maschad Apr 10, 2024
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
5 changes: 5 additions & 0 deletions .changeset/flat-peaches-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/program": minor
---

feat: add `isReadOnly` helper for functions
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects';
import { createAndDeployContractFromProject } from '../../utils';

/**
* @group node
*/
test('isReadOnly returns true for read-only functions', async () => {
const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.COUNTER);

// #region is-function-readonly-1
const isReadOnly = contract.functions.get_count.isReadOnly();

if (isReadOnly) {
await contract.functions.get_count().get();
} else {
await contract.functions.get_count().call();
}
// #endregion is-function-readonly-1

expect(isReadOnly).toBe(true);
});
10 changes: 10 additions & 0 deletions apps/docs/src/guide/contracts/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,13 @@ The `call` method should be used to submit a real contract call transaction to t
Real resources are consumed, and any operations executed by the contract function will be processed on the blockchain.

<<< @/../../docs-snippets/src/guide/contracts/interacting-with-contracts.test.ts#interacting-with-contracts-4{ts:line-numbers}

## `isReadOnly` (utility)

If you want to figure out whether a function is read-only, you can use the `isReadOnly` method:

<<< @/../../docs-snippets/src/guide/contracts/is-function-readonly.test.ts#is-function-readonly-1{ts:line-numbers}

If the function is read-only or pure, you can use the `get` method to read the data from the blockchain without using resources.
Dhaiwat10 marked this conversation as resolved.
Show resolved Hide resolved

If the function is not read-only, you can use the `call` method to submit a 'real' transaction to the blockchain which _will_ consume resources.
Dhaiwat10 marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 10 additions & 0 deletions packages/abi-coder/src/FunctionFragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,14 @@ export class FunctionFragment<

return coder.decode(bytes, 0) as [DecodedValue | undefined, number];
}

/**
* Checks if the function is read-only i.e. it only reads from storage, does not write to it.
*
* @returns True if the function is read-only or pure, false otherwise.
*/
isReadOnly(): boolean {
const storageAttribute = this.attributes.find((attr) => attr.name === 'storage');
return !storageAttribute?.arguments.includes('write');
maschad marked this conversation as resolved.
Show resolved Hide resolved
}
}
32 changes: 32 additions & 0 deletions packages/fuel-gauge/src/is-function-readonly.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FuelGaugeProjectsEnum } from '../test/fixtures';

import { getSetupContract } from './utils';

/**
* @group node
*/
describe('isReadOnly', () => {
test('isReadOnly returns true for a read-only function', async () => {
const contract = await getSetupContract(FuelGaugeProjectsEnum.STORAGE_TEST_CONTRACT)();

const isReadOnly = contract.functions.counter.isReadOnly();

expect(isReadOnly).toBe(true);
});

test('isReadOnly returns false for a function containing write operations', async () => {
const contract = await getSetupContract(FuelGaugeProjectsEnum.STORAGE_TEST_CONTRACT)();

const isReadOnly = contract.functions.increment_counter.isReadOnly();

expect(isReadOnly).toBe(false);
});

test('isReadOnly does not throw a runtime error for a function that does not use storage', async () => {
const contract = await getSetupContract(FuelGaugeProjectsEnum.STORAGE_TEST_CONTRACT)();

const isReadOnly = contract.functions.return_true.isReadOnly();

expect(isReadOnly).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ abi StorageTestContract {
fn return_var4() -> bool;
#[storage(read)]
fn return_var5() -> StructValidation;
fn return_true() -> bool;
}

const COUNTER_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;
Expand Down Expand Up @@ -88,4 +89,7 @@ impl StorageTestContract for Contract {
fn return_var5() -> StructValidation {
storage.var5.read()
}
fn return_true() -> bool {
true
}
}
14 changes: 12 additions & 2 deletions packages/program/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { AbstractAddress, AbstractContract, BytesLike } from '@fuel-ts/inte

import { FunctionInvocationScope } from './functions/invocation-scope';
import { MultiCallInvocationScope } from './functions/multicall-scope';
import type { InvokeFunctions } from './types';
import type { InvokeFunction, InvokeFunctions } from './types';

/**
* `Contract` provides a way to interact with the contract program type.
Expand Down Expand Up @@ -89,7 +89,17 @@ export default class Contract implements AbstractContract {
* @returns A function that creates a FunctionInvocationScope.
*/
buildFunction(func: FunctionFragment) {
return (...args: Array<unknown>) => new FunctionInvocationScope(this, func, args);
return (() => {
const funcInvocationScopeCreator = (...args: Array<unknown>) =>
new FunctionInvocationScope(this, func, args);

Object.defineProperty(funcInvocationScopeCreator, 'isReadOnly', {
value: () => func.isReadOnly(),
writable: false,
});

return funcInvocationScopeCreator;
})() as InvokeFunction;
Dhaiwat10 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
7 changes: 4 additions & 3 deletions packages/program/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ export type CallConfig<T = unknown> = {
* @template TReturn - Type of the function's return value.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InvokeFunction<TArgs extends Array<any> = Array<any>, TReturn = any> = (
...args: TArgs
) => FunctionInvocationScope<TArgs, TReturn>;
export interface InvokeFunction<TArgs extends Array<any> = Array<any>, TReturn = any> {
(...args: TArgs): FunctionInvocationScope<TArgs, TReturn>;
isReadOnly: () => boolean;
}

/**
* Represents a collection of functions that can be invoked.
Expand Down
Loading