Skip to content

Commit 0de1c32

Browse files
zees-devsdankelJoshuaBatty
authored
plugin: forc-call (#6791)
## Description The following PR introduces a new forc plugin; `forc-call`. This plugin allows users to call functions on deployed contracts using the `forc call` command. This is ideal for quickly querying the state of a deployed contract. In this first implementation; the contract ABI is required (as a path to a local JSON file or a URL to a remote JSON file). This is inspired by the [`cast call`](https://book.getfoundry.sh/reference/cast/cast-call) tool; which is a popular tool for interacting with deployed contracts on Ethereum. The implementation is based on the following Github issue: #6725 In the current implementation, you can query a contract state using the `forc call` command by providing the target contract ID, it's respective ABI file, and the function name (selector) and arguments. <details> <summary>Forc Call CLI</summary> ```sh forc call --help ``` ``` Call a contract function Usage: forc call [OPTIONS] <CONTRACT_ID> <FUNCTION> [ARGS]... Arguments: <CONTRACT_ID> The contract ID to call <FUNCTION> The function signature to call. When ABI is provided, this should be a selector (e.g. "transfer") When no ABI is provided, this should be the full function signature (e.g. "transfer(address,u64)") [ARGS]... Arguments to pass into main function with forc run Options: --abi <ABI> Optional path or URI to a JSON ABI file --node-url <NODE_URL> The URL of the Fuel node to which we're submitting the transaction. If unspecified, checks the manifest's `network` table, then falls back to `http://127.0.0.1:4000` You can also use `--target`, `--testnet`, or `--mainnet` to specify the Fuel node. [env: FUEL_NODE_URL=] --target <TARGET> Use preset configurations for deploying to a specific target. You can also use `--node-url`, `--testnet`, or `--mainnet` to specify the Fuel node. Possible values are: [local, testnet, mainnet] --testnet Use preset configuration for testnet. You can also use `--node-url`, `--target`, or `--mainnet` to specify the Fuel node. --mainnet Use preset configuration for mainnet. You can also use `--node-url`, `--target`, or `--testnet` to specify the Fuel node. --signing-key <SIGNING_KEY> Derive an account from a secret key to make the call [env: SIGNING_KEY=] --wallet Use forc-wallet to make the call --amount <AMOUNT> Amount of native assets to forward with the call [default: 0] --asset-id <ASSET_ID> Asset ID to forward with the call --gas-forwarded <GAS_FORWARDED> Amount of gas to forward with the call --mode <MODE> The execution mode to use for the call; defaults to dry-run; possible values: dry-run, simulate, live [default: dry-run] --gas-price <PRICE> Gas price for the transaction --script-gas-limit <SCRIPT_GAS_LIMIT> Gas limit for the transaction --max-fee <MAX_FEE> Max fee for the transaction --tip <TIP> The tip for the transaction --external-contracts <EXTERNAL_CONTRACTS> The external contract addresses to use for the call If none are provided, the call will automatically extract contract addresses from the function arguments and use them for the call as external contracts -h, --help Print help (see a summary with '-h') -V, --version Print version ``` </details> ### Example usage ```sh forc call 0xe18de7c7c8c61a1c706dccb3533caa00ba5c11b5230da4428582abf1b6831b4d --abi ./out/debug/counter-contract-abi.json add 1 2 ``` - where `0xe18de7c7c8c61a1c706dccb3533caa00ba5c11b5230da4428582abf1b6831b4d` is the contract ID - where `./out/debug/counter-contract-abi.json` is the path to the ABI file - where `add` is the function name (selector) - where `1 2` are the arguments to the function ^ the sway code for the add function could be: ```sway contract; abi MyContract { fn add(a: u64, b: u64) -> u64; } impl MyContract for Contract { fn add(a: u64, b: u64) -> u64 { a + b } } ``` ## Implementation details 1. The provided ABI file downloaded (unless local path is provided) 2. The ABI is parsed into internal representation 3. The provided function selector e.g. `add` is matched with the extracted functions from the ABI 4. The provided arguments are parsed into the appropriate types which match the extracted function's inputs 5. The function selector and args are then converted into the `Token` enum, which is then ABI encoded as part of the `ContractCall` struct 6. The `ContractCall` struct is then used to make a request to the node to call the function 7. The response is then decoded into the appropriate type (based on matched function's output type) ^ In the implementation, we don't use the `abigen!` macro since this is a compile time parser of the ABI file; instead we make use of the lower level encoding and decoding primitives and functions from the [Rust SDK](https://github.com/FuelLabs/fuels-rs). ## Live example on testnet ### Example 1 The example contract above with `add` function has been deployed on testnet - with ABI file available [here](https://pastebin.com/raw/XY3awY3T). The add function can be called via the CLI: ```sh cargo run -p forc-client --bin call -- \ --testnet \ --abi https://pastebin.com/raw/XY3awY3T \ 0xe18de7c7c8c61a1c706dccb3533caa00ba5c11b5230da4428582abf1b6831b4d \ add 1 2 ``` ### Example 2 - get `owner` of Mira DEX contract ```sh cargo run -p forc-client --bin call -- \ --testnet \ --abi https://raw.githubusercontent.com/mira-amm/mira-v1-periphery/refs/heads/main/fixtures/mira-amm/mira_amm_contract-abi.json \ 0xd5a716d967a9137222219657d7877bd8c79c64e1edb5de9f2901c98ebe74da80 \ owner ``` Note: Testnet contract address [here](https://docs.mira.ly/developer-guides/developer-overview#testnet-deployment) ## Encoding of primitive types When passing in function arguments, the following types are encoded as follows: | Types | Example input | Notes | |-----------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | bool | `true` or `false` | | | u8, u16, u32, u64, u128, u256 | `42` | | | b256 | `0x0000000000000000000000000000000000000000000000000000000000000042` or `0000000000000000000000000000000000000000000000000000000000000042` | `0x` prefix is optional | | bytes, RawSlice | `0x42` or `42` | `0x` prefix is optional | | String, StringSlice, StringArray (Fixed-size) | `"abc"` | | | Tuple | `(42, true)` | The types in tuple can be different | | Array (Fixed-size), Vector (Dynamic) | `[42, 128]` | The types in array or vector must be the same; i.e. you cannot have `[42, true]` | | Struct | `{42, 128}` | Since structs are packed encoded, the attribute names are not encoded; i.e. `{42, 128}`; this could represent the following `struct Polygon { x: u64, y: u64 }` | | Enum | `(Active: true)` or `(1: true)` | Enums are key-val pairs with keys as being variant name (case-sensitive) or variant index (starting from 0) and values as being the variant value; this could represent the following `enum MyEnum { Inactive, Active(bool) }` | <details> <summary>Encoding cheat-sheet</summary> A few of the common types are encoded as follows: | Types | Encoding Description | Example | |--------------------------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| | bool, u8 | Encoded as a single byte. `bool`: 0x00 (false) or 0x01 (true); `u8` is the byte itself. | `bool(true) = 0x01`, `u8(42) = 0x2A` | | u16 | 2-byte, big-endian | `u16(42) = 0x002A` | | u32 | 4-byte, big-endian | `u32(42) = 0x0000002A` | | u64 | 8-byte, big-endian | `u64(42) = 0x000000000000002A` | | u128 | 16-byte, big-endian | `u128(42) = 0x0000000000000000000000000000002A` | | u256, b256 | 32-byte value. For u256: big-endian integer; For b256: raw 32 bytes | `u256(42) = 32-bytes ending with ...0000002A`, `b256(...) = exactly the 32-byte array` | | Tuples, Arrays, Structs (Fixed-size) | Concatenate the encodings of each element/field with no extra padding | `(u8(1), bool(true)) = 0x01 0x01`; `[u16;2]: [42,100] = 0x002A0064`; `struct {u8,u8}: 0x0102` | | Enums | `u64` variant index + encoded variant data with no extra padding | `MySumType::X(42): 0x0000000000000000 000000000000002A` | | Bytes, String, RawSlice, Vector (Dynamic) | `u64` length prefix + raw data, no padding | `"abc" = length=3: 0x0000000000000003 0x61 0x62 0x63` | ^ This is based on the docs here: https://docs.fuel.network/docs/specs/abi/argument-encoding </details> ## Future improvements 1. Support for function signature based calls without ABI 2. Support for raw calldata input 3. Function selector completion - given ABI file ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [ ] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: zees-dev <zees-dev@users.noreply.github.com> Co-authored-by: Sophie Dankel <47993817+sdankel@users.noreply.github.com> Co-authored-by: Joshua Batty <joshpbatty@gmail.com>
1 parent 83d999a commit 0de1c32

File tree

27 files changed

+4034
-70
lines changed

27 files changed

+4034
-70
lines changed

Cargo.lock

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/book/spell-check-custom-words.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,8 @@ FuelLabs
237237
github
238238
toml
239239
hardcoded
240-
subdirectories
240+
subdirectories
241+
RawSlice
242+
StringArray
243+
StringSlice
244+
calldata

docs/book/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
- [forc deploy](./forc/plugins/forc_client/forc_deploy.md)
100100
- [forc run](./forc/plugins/forc_client/forc_run.md)
101101
- [forc submit](./forc/plugins/forc_client/forc_submit.md)
102+
- [forc call](./forc/plugins/forc_client/forc_call.md)
102103
- [forc crypto](./forc/plugins/forc_crypto.md)
103104
- [forc debug](./forc/plugins/forc_debug.md)
104105
- [forc doc](./forc/plugins/forc_doc.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# Forc Call
2+
3+
`forc-call` is a command-line tool for interacting with deployed Fuel contracts. It allows you to make contract calls, query contract state, and interact with any deployed contract on the Fuel network - all from your command line!
4+
5+
The `forc call` command is part of the Forc toolchain and is installed alongside other Forc tools.
6+
7+
## Getting Started
8+
9+
Here are a few examples of what you can do with `forc call`:
10+
11+
Call a simple addition function on a deployed contract (in dry-run mode):
12+
13+
```bash
14+
forc call 0xe18de7c7c8c61a1c706dccb3533caa00ba5c11b5230da4428582abf1b6831b4d \
15+
--abi ./out/debug/counter-contract-abi.json \
16+
add 1 2
17+
```
18+
19+
Query the owner of a deployed DEX contract on testnet:
20+
21+
```bash
22+
forc call \
23+
--testnet \
24+
--abi https://raw.githubusercontent.com/mira-amm/mira-v1-periphery/refs/heads/main/fixtures/mira-amm/mira_amm_contract-abi.json \
25+
0xd5a716d967a9137222219657d7877bd8c79c64e1edb5de9f2901c98ebe74da80 \
26+
owner
27+
```
28+
29+
## Usage
30+
31+
The basic syntax for `forc call` is:
32+
33+
```bash
34+
forc call [OPTIONS] --abi <ABI-PATH/URL> <CONTRACT_ID> <SELECTOR> [ARGS]...
35+
```
36+
37+
Where the following arguments are required:
38+
39+
- `CONTRACT_ID` is the ID of the deployed contract you want to interact with
40+
- `ABI-PATH/URL` is the path or URL to the contract's JSON ABI file
41+
- `SELECTOR` is the function name (selector) you want to call
42+
- `ARGS` are the arguments to pass to the function
43+
44+
## CLI Reference
45+
46+
<details>
47+
<summary><b>Forc Call CLI reference</b></summary>
48+
49+
```sh
50+
forc call --help
51+
```
52+
53+
```output
54+
Perform Fuel RPC calls from the comfort of your command line
55+
56+
Usage: forc call [OPTIONS] --abi <ABI> <CONTRACT_ID> <FUNCTION> [FUNCTION_ARGS]...
57+
58+
Arguments:
59+
<CONTRACT_ID>
60+
The contract ID to call
61+
62+
<FUNCTION>
63+
The function signature to call. When ABI is provided, this should be a selector (e.g. "transfer") When no ABI is provided, this should be the full function signature (e.g. "transfer(address,u64)")
64+
65+
[FUNCTION_ARGS]...
66+
Arguments to pass into the function to be called
67+
68+
Options:
69+
--abi <ABI>
70+
Path or URI to a JSON ABI file
71+
72+
--node-url <NODE_URL>
73+
The URL of the Fuel node to which we're submitting the transaction. If unspecified, checks the manifest's `network` table, then falls back to `http://127.0.0.1:4000`
74+
75+
You can also use `--target`, `--testnet`, or `--mainnet` to specify the Fuel node.
76+
77+
[env: FUEL_NODE_URL=]
78+
79+
--target <TARGET>
80+
Preset configurations for using a specific target.
81+
82+
You can also use `--node-url`, `--testnet`, or `--mainnet` to specify the Fuel node.
83+
84+
Possible values are: [local, testnet, mainnet]
85+
86+
--mainnet
87+
Use preset configuration for mainnet.
88+
89+
You can also use `--node-url`, `--target`, or `--testnet` to specify the Fuel node.
90+
91+
--testnet
92+
Use preset configuration for testnet.
93+
94+
You can also use `--node-url`, `--target`, or `--mainnet` to specify the Fuel node.
95+
96+
--devnet
97+
Use preset configuration for devnet.
98+
99+
You can also use `--node-url`, `--target`, or `--testnet` to specify the Fuel node.
100+
101+
--signing-key <SIGNING_KEY>
102+
Derive an account from a secret key to make the call
103+
104+
[env: SIGNING_KEY=]
105+
106+
--wallet
107+
Use forc-wallet to make the call
108+
109+
--amount <AMOUNT>
110+
Amount of native assets to forward with the call
111+
112+
[default: 0]
113+
114+
--asset-id <ASSET_ID>
115+
Asset ID to forward with the call
116+
117+
--gas-forwarded <GAS_FORWARDED>
118+
Amount of gas to forward with the call
119+
120+
--mode <MODE>
121+
The execution mode to use for the call; defaults to dry-run; possible values: dry-run, simulate, live
122+
123+
[default: dry-run]
124+
125+
--gas-price <PRICE>
126+
Gas price for the transaction
127+
128+
--script-gas-limit <SCRIPT_GAS_LIMIT>
129+
Gas limit for the transaction
130+
131+
--max-fee <MAX_FEE>
132+
Max fee for the transaction
133+
134+
--tip <TIP>
135+
The tip for the transaction
136+
137+
--external-contracts <EXTERNAL_CONTRACTS>
138+
The external contract addresses to use for the call If none are provided, the call will automatically populate external contracts by making a dry-run calls to the node, and extract the contract addresses based on the revert reason
139+
140+
--output <OUTPUT>
141+
The output format to use; possible values: default, raw
142+
143+
[default: default]
144+
145+
-h, --help
146+
Print help (see a summary with '-h')
147+
148+
-V, --version
149+
Print version
150+
```
151+
152+
</details>
153+
154+
## Type Encoding
155+
156+
When passing arguments to contract functions, values are encoded according to their Sway types.
157+
Here's how to format different types:
158+
159+
| Types | Example input | Notes |
160+
|-----------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
161+
| bool | `true` or `false` | |
162+
| u8, u16, u32, u64, u128, u256 | `42` | |
163+
| b256 | `0x0000000000000000000000000000000000000000000000000000000000000042` or `0000000000000000000000000000000000000000000000000000000000000042` | `0x` prefix is optional |
164+
| bytes, RawSlice | `0x42` or `42` | `0x` prefix is optional |
165+
| String, StringSlice, StringArray (Fixed-size) | `"abc"` | |
166+
| Tuple | `(42, true)` | The types in tuple can be different |
167+
| Array (Fixed-size), Vector (Dynamic) | `[42, 128]` | The types in array or vector must be the same; i.e. you cannot have `[42, true]` |
168+
| Struct | `{42, 128}` | Since structs are packed encoded, the attribute names are not encoded; i.e. `{42, 128}`; this could represent the following `struct Polygon { x: u64, y: u64 }` |
169+
| Enum | `(Active: true)` or `(1: true)` | Enums are key-val pairs with keys as being variant name (case-sensitive) or variant index (starting from 0) and values as being the variant value; this could represent the following `enum MyEnum { Inactive, Active(bool) }` |
170+
171+
## ABI Support
172+
173+
The ABI (Application Binary Interface) can be provided in two ways.
174+
175+
### Local file
176+
177+
```bash
178+
forc call <CONTRACT_ID> --abi ./path/to/abi.json <FUNCTION> [ARGS...]
179+
```
180+
181+
### Remote ABI file/URL
182+
183+
```bash
184+
forc call <CONTRACT_ID> --abi https://example.com/abi.json <FUNCTION> [ARGS...]
185+
```
186+
187+
## Network Configuration
188+
189+
```bash
190+
forc call --node-url http://127.0.0.1:4000 ...
191+
# or
192+
forc call --target local ...
193+
```
194+
195+
## Advanced Usage
196+
197+
### Using Wallets
198+
199+
```sh
200+
# utilising the forc-wallet
201+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --wallet
202+
```
203+
204+
```sh
205+
# with an explicit signing key
206+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --signing-key <KEY>
207+
```
208+
209+
### Asset Transfers
210+
211+
```sh
212+
# Native asset transfer
213+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --amount 100 --live
214+
```
215+
216+
```sh
217+
# Custom asset transfer
218+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> \
219+
--amount 100 \
220+
--asset-id 0x1234... \
221+
--live
222+
```
223+
224+
### Gas Configuration
225+
226+
```sh
227+
# Set gas price
228+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --gas-price 1
229+
230+
# Forward gas to contract
231+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --gas-forwarded 1000
232+
233+
# Set maximum fee
234+
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --max-fee 5000
235+
```
236+
237+
### Common Use Cases
238+
239+
- 1. Contract State Queries
240+
241+
```sh
242+
# Read contract state
243+
forc call <CONTRACT_ID> --abi <PATH> get_balance
244+
245+
# Query with parameters
246+
forc call <CONTRACT_ID> --abi <PATH> get_user_info 0x1234...
247+
```
248+
249+
- 2. Token Operations
250+
251+
```sh
252+
# Check token balance
253+
forc call <CONTRACT_ID> --abi <PATH> balance_of 0x1234...
254+
255+
# Transfer tokens
256+
forc call <CONTRACT_ID> --abi <PATH> transfer 0x1234... 100 --live
257+
```
258+
259+
- 3. Contract Administration
260+
261+
```sh
262+
# Check contract owner
263+
forc call <CONTRACT_ID> --abi <PATH> owner
264+
265+
# Update contract parameters
266+
forc call <CONTRACT_ID> --abi <PATH> update_params 42 --live
267+
```
268+
269+
## Tips and Tricks
270+
271+
- Use `--mode simulate` to estimate gas costs before making live transactions
272+
- External contracts are automatically detected (via internal simulations), but can be manually specified with `--external-contracts`
273+
- For complex parameter types (tuples, structs, enums), refer to the parameter types table above
274+
- Always verify contract addresses and ABIs before making live calls
275+
- Use environment variables for sensitive data like signing keys: `SIGNING_KEY=<key>`
276+
277+
## Troubleshooting
278+
279+
### Common issues and solutions
280+
281+
- **ABI Mismatch**:
282+
- Ensure the ABI matches the deployed contract
283+
- Verify function selectors match exactly
284+
285+
- **Parameter Type Errors**:
286+
- Check parameter formats in the types table
287+
- Ensure correct number of parameters
288+
289+
- **Network Issues**:
290+
- Verify node connection
291+
- Check network selection (testnet/mainnet)
292+
293+
- **Transaction Failures**:
294+
- Use simulation mode to debug
295+
- Check gas settings
296+
- Verify wallet has sufficient balance
297+
298+
## Future Features
299+
300+
The following features are planned for future releases:
301+
302+
- Decode and display logs for contract calls
303+
- Support direct transfer of asset(s) to addresses
304+
- Function signature based calls without ABI
305+
- Raw calldata input support
306+
- Function selector completion
307+
- Enhanced error messages, debugging, and logging (additional verbosity modes)

0 commit comments

Comments
 (0)