Conversation
📝 WalkthroughWalkthroughThis PR introduces a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
tests/src/tests/defuse/arbitrary.rs (1)
29-59: Cover the new deposit guard explicitly.This suite never exercises the
env::attached_deposit() >= action.get_deposit()check indefuse/src/contract/arbitrary.rs, Lines 15-18. Add a case where the outerdepositis smaller thanFunctionCallAction.depositso a regression there does not hide behind the permission checks.I can draft that test if helpful.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/src/tests/defuse/arbitrary.rs` around lines 29 - 59, The test never exercises the deposit guard (env::attached_deposit() >= action.get_deposit()) in arbitrary.rs; add a case before granting DAO role that calls admin.arbitrary_call(...) with an outer deposit smaller than the FunctionCallAction.deposit (set FunctionCallAction.deposit > deposit passed to arbitrary_call) and assert it returns an error; reference the FunctionCallAction struct's deposit field and the arbitrary_call method to locate where to insert this failing call and optionally assert the contract balance remains unchanged.defuse/src/arbitrary.rs (1)
19-31: Clarify the public API’s target, auth, and funding semantics.
arbitrary_calldispatchesPromise::new(account_id), so the action runs againstaccount_id, not “on the contract”. It would also help to mention that the contract implementation is DAO-gated and thatTransferspends contract balance because this variant reportsNearToken::ZEROas the required attached deposit.✏️ Suggested doc update
#[ext_contract(ext_arbitrary_manager)] pub trait ArbitraryManager { - /// Allows the caller to execute an arbitrary function call - /// or transfer on the contract. + /// DAO-only entrypoint that dispatches `action` from this contract to `account_id`. + /// `FunctionCall` forwards its attached deposit; `Transfer` spends this contract's NEAR balance. fn arbitrary_call(&mut self, account_id: AccountId, action: ArbitraryAction) -> Promise; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@defuse/src/arbitrary.rs` around lines 19 - 31, The public API docs for ArbitraryManager::arbitrary_call and the ArbitraryAction enum are ambiguous about where the action executes, who can authorize it, and who funds a Transfer; update the trait and related comments to state that arbitrary_call dispatches Promise::new(account_id) so the action executes against the provided account_id (not "on the contract"), that execution is gated by the contract's DAO authorization (mention the DAO check in the implementation), and clarify funding: ArbitraryAction::Transfer spends contract balance (get_deposit returns NearToken::ZERO for Transfer, meaning callers need not attach deposit but the contract funds the transfer) while FunctionCall requires the caller to attach the required deposit (reference get_deposit and the FunctionCall/Transfer variants to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@actions/src/lib.rs`:
- Around line 39-72: The struct FunctionCallAction uses field-level
#[serde_as(...)] but lacks the container-level #[serde_as] attribute and the
serde_with import; add the container attribute #[serde_as] above the
FunctionCallAction definition and import serde_with::serde_as (and any
serde_with helpers used like DisplayFromStr/Base64 if not already imported) so
the field-level serde_with attributes (on args and gas_weight) are wired
correctly and the code compiles.
- Around line 93-107: The methods args_json and args_borsh currently call
unwrap() on fallible serializers causing panics; replace these with
non-panicking alternatives by adding fallible variants (e.g., try_args_json and
try_args_borsh) that perform serde_json::to_vec and borsh::to_vec and return
Result<Self, E> (propagate or map serialization errors into a suitable error
type), and keep or deprecate the existing convenience methods to call the try_
variants internally if desired; update the builder signatures (args_json,
args_borsh, or their try_ counterparts) to return Result so callers can handle
serialization failures instead of panicking, and reference these functions
(args_json, args_borsh, try_args_json, try_args_borsh) when making the change.
In `@defuse/Cargo.toml`:
- Around line 75-76: The `arbitrary` feature in Cargo.toml needs to propagate
into defuse-actions so the Arbitrary derives for FunctionCallAction and
TransferAction are enabled; update the `arbitrary` feature list to include
"defuse-actions/arbitrary" (so consumers enabling `defuse/arbitrary` also enable
the gated derives/attributes used in actions/src/lib.rs and
actions/src/arbitrary.rs), ensuring defuse/src/arbitrary.rs re-exports have
complete Arbitrary implementations.
---
Nitpick comments:
In `@defuse/src/arbitrary.rs`:
- Around line 19-31: The public API docs for ArbitraryManager::arbitrary_call
and the ArbitraryAction enum are ambiguous about where the action executes, who
can authorize it, and who funds a Transfer; update the trait and related
comments to state that arbitrary_call dispatches Promise::new(account_id) so the
action executes against the provided account_id (not "on the contract"), that
execution is gated by the contract's DAO authorization (mention the DAO check in
the implementation), and clarify funding: ArbitraryAction::Transfer spends
contract balance (get_deposit returns NearToken::ZERO for Transfer, meaning
callers need not attach deposit but the contract funds the transfer) while
FunctionCall requires the caller to attach the required deposit (reference
get_deposit and the FunctionCall/Transfer variants to locate the code).
In `@tests/src/tests/defuse/arbitrary.rs`:
- Around line 29-59: The test never exercises the deposit guard
(env::attached_deposit() >= action.get_deposit()) in arbitrary.rs; add a case
before granting DAO role that calls admin.arbitrary_call(...) with an outer
deposit smaller than the FunctionCallAction.deposit (set
FunctionCallAction.deposit > deposit passed to arbitrary_call) and assert it
returns an error; reference the FunctionCallAction struct's deposit field and
the arbitrary_call method to locate where to insert this failing call and
optionally assert the contract balance remains unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1d1d2043-2ff8-4b7c-9e73-25c945f098a0
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
Cargo.tomlactions/Cargo.tomlactions/src/arbitrary.rsactions/src/lib.rsdefuse/Cargo.tomldefuse/src/arbitrary.rsdefuse/src/contract/arbitrary.rsdefuse/src/contract/mod.rsdefuse/src/lib.rssandbox/src/extensions/defuse/arbitrary.rssandbox/src/extensions/defuse/mod.rstests/Cargo.tomltests/src/tests/defuse/arbitrary.rstests/src/tests/defuse/mod.rs
| #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] | ||
| #[near(serializers = [borsh, json])] | ||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
| pub struct FunctionCallAction { | ||
| pub function_name: String, | ||
|
|
||
| #[cfg_attr( | ||
| all(feature = "abi", not(target_arch = "wasm32")), | ||
| schemars(with = "String") | ||
| )] | ||
| #[serde_as(as = "Base64")] | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub args: Vec<u8>, | ||
|
|
||
| #[cfg_attr( | ||
| feature = "arbitrary", | ||
| arbitrary(with = crate::arbitrary::near_token), | ||
| )] | ||
| #[serde(default, skip_serializing_if = "NearToken::is_zero")] | ||
| pub deposit: NearToken, | ||
|
|
||
| #[cfg_attr( | ||
| feature = "arbitrary", | ||
| arbitrary(with = crate::arbitrary::gas), | ||
| )] | ||
| #[serde(default, skip_serializing_if = "Gas::is_zero")] | ||
| pub min_gas: Gas, | ||
|
|
||
| #[serde_as(as = "DisplayFromStr")] | ||
| #[serde( | ||
| default = "default_gas_weight", | ||
| skip_serializing_if = "is_default_gas_weight" | ||
| )] | ||
| pub gas_weight: u64, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect existing serde_with macro usage in this crate.
rg -n -C2 'serde_as|extern crate serde_with|macro_use' actions/srcRepository: near/intents
Length of output: 549
🏁 Script executed:
head -40 actions/src/lib.rsRepository: near/intents
Length of output: 1005
🏁 Script executed:
sed -n '39,80p' actions/src/lib.rsRepository: near/intents
Length of output: 1262
🏁 Script executed:
cat actions/Cargo.toml | head -50Repository: near/intents
Length of output: 450
🏁 Script executed:
# Check if there are any build artifacts or CI logs indicating compile status
git log --oneline -n 5 actions/src/lib.rsRepository: near/intents
Length of output: 70
Wire up serde_with::serde_as correctly.
Lines 49 and 67 use field-level #[serde_as(...)] attributes, but FunctionCallAction is missing the container-level #[serde_as] macro, and serde_as is not imported. As written, the serde-with configuration is incomplete and will not compile.
Suggested fix
use near_sdk::{
Gas, GasWeight, NearToken, Promise,
borsh::{self, BorshSerialize},
near,
serde::Serialize,
serde_json,
- serde_with::{DisplayFromStr, base64::Base64},
+ serde_with::{serde_as, DisplayFromStr, base64::Base64},
};
...
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
+#[serde_as]
#[near(serializers = [borsh, json])]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FunctionCallAction {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@actions/src/lib.rs` around lines 39 - 72, The struct FunctionCallAction uses
field-level #[serde_as(...)] but lacks the container-level #[serde_as] attribute
and the serde_with import; add the container attribute #[serde_as] above the
FunctionCallAction definition and import serde_with::serde_as (and any
serde_with helpers used like DisplayFromStr/Base64 if not already imported) so
the field-level serde_with attributes (on args and gas_weight) are wired
correctly and the code compiles.
| #[must_use] | ||
| pub fn args_json<T>(self, args: T) -> Self | ||
| where | ||
| T: Serialize, | ||
| { | ||
| self.args(serde_json::to_vec(&args).unwrap()) | ||
| } | ||
|
|
||
| #[must_use] | ||
| pub fn args_borsh<T>(self, args: T) -> Self | ||
| where | ||
| T: BorshSerialize, | ||
| { | ||
| self.args(borsh::to_vec(&args).unwrap()) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find current call sites before making these builders fallible.
rg -nP -C2 '\.(args_json|args_borsh)\s*\('Repository: near/intents
Length of output: 38
🏁 Script executed:
cat -n actions/src/lib.rs | sed -n '85,115p'Repository: near/intents
Length of output: 893
🏁 Script executed:
# Check if try_args_json or try_args_borsh variants already exist
rg -nP '(try_args_json|try_args_borsh)'Repository: near/intents
Length of output: 38
🏁 Script executed:
# Search more broadly for args_json usage across the repo
rg -nP 'args_json' --type rustRepository: near/intents
Length of output: 124
🏁 Script executed:
# Check if ActionBuilder is exported from lib.rs and understand the public API
head -50 actions/src/lib.rsRepository: near/intents
Length of output: 1322
🏁 Script executed:
# Check Cargo.toml to understand if this is a public library
cat actions/Cargo.tomlRepository: near/intents
Length of output: 450
🏁 Script executed:
# Find the ActionBuilder struct definition to understand the full context
rg -nP 'struct ActionBuilder' -A 15Repository: near/intents
Length of output: 38
🏁 Script executed:
# Search for ActionBuilder more broadly
rg -nP 'ActionBuilder' -A 5Repository: near/intents
Length of output: 38
🏁 Script executed:
# Look for the struct definition that contains args_json method
rg -nP 'struct\s+\w+' actions/src/lib.rsRepository: near/intents
Length of output: 121
🏁 Script executed:
# Find what struct the args_json method belongs to by looking around lines 93-107
sed -n '60,95p' actions/src/lib.rsRepository: near/intents
Length of output: 958
🏁 Script executed:
# Check if there are impl blocks
rg -nP 'impl\s+' actions/src/lib.rs -A 2Repository: near/intents
Length of output: 606
🏁 Script executed:
# Check what's exported from the crate's lib.rs module
tail -50 actions/src/lib.rsRepository: near/intents
Length of output: 1165
🏁 Script executed:
# Check if FunctionCallAction is re-exported in the workspace root
find . -name "lib.rs" -type f | head -5 | xargs grep -l "FunctionCallAction" 2>/dev/nullRepository: near/intents
Length of output: 76
🏁 Script executed:
# Check the root lib.rs or mod declarations to confirm public API exposure
rg -nP 'pub.*mod\s+actions|pub.*use.*FunctionCallAction' . --type rustRepository: near/intents
Length of output: 38
🏁 Script executed:
# Check if there are any comments or TODOs about error handling in this file
rg -nP '(TODO|FIXME|XXX|HACK).*(?:error|unwrap|panic)' actions/src/lib.rsRepository: near/intents
Length of output: 38
🏁 Script executed:
# Check Cargo.toml at the workspace root to understand package structure
cat Cargo.tomlRepository: near/intents
Length of output: 3892
🏁 Script executed:
# Check actions/Cargo.toml to see if it's published/public
cat actions/Cargo.tomlRepository: near/intents
Length of output: 450
🏁 Script executed:
# Look for README or documentation that describes the library purpose
find actions -name "README*" -o -name "*.md" | head -5Repository: near/intents
Length of output: 38
🏁 Script executed:
# Search for whether borsh::to_vec or serde_json::to_vec can fail
# by checking their documentation in the nearcore/SDK or common patterns
rg -nP 'Result.*to_vec' . --type rust | head -10Repository: near/intents
Length of output: 38
🏁 Script executed:
# Check if serde_json::to_vec can actually fail - look at its usage in tests or examples
rg -nP 'to_vec' actions/src/lib.rs -B 2 -A 2Repository: near/intents
Length of output: 272
🏁 Script executed:
# Verify what serialization errors these libraries can produce
# by checking if there are any error handling patterns elsewhere
rg -nP 'serde_json::.*Result|borsh::.*Result' . --type rust | head -5Repository: near/intents
Length of output: 178
🏁 Script executed:
# Verify the signature of serde_json::to_vec and borsh::to_vec
# by checking what near-sdk exports
rg -nP '(pub fn to_vec|type.*Result).*serde_json|borsh' /dev/stdin 2>/dev/null || echo "Checking near-sdk docs..."
# Instead, search for any documentation or patterns in codebase
rg -nP 'serde_json::to_vec.*\?' . --type rustRepository: near/intents
Length of output: 81
🏁 Script executed:
# Check if there are any serialization error patterns in the codebase
rg -nP 'Error|serde_json.*error|borsh.*error' actions/src/lib.rsRepository: near/intents
Length of output: 38
Avoid panicking on serialization errors in public builder methods.
The args_json and args_borsh methods call unwrap() on fallible serializers (lines 98 and 106). Both serde_json::to_vec and borsh::to_vec return Result types and can fail for valid serializable objects. In a public library API, callers cannot handle these panics. Consider providing fallible variants (try_args_json / try_args_borsh) that return Result, or add a non-panicking error handling path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@actions/src/lib.rs` around lines 93 - 107, The methods args_json and
args_borsh currently call unwrap() on fallible serializers causing panics;
replace these with non-panicking alternatives by adding fallible variants (e.g.,
try_args_json and try_args_borsh) that perform serde_json::to_vec and
borsh::to_vec and return Result<Self, E> (propagate or map serialization errors
into a suitable error type), and keep or deprecate the existing convenience
methods to call the try_ variants internally if desired; update the builder
signatures (args_json, args_borsh, or their try_ counterparts) to return Result
so callers can handle serialization failures instead of panicking, and reference
these functions (args_json, args_borsh, try_args_json, try_args_borsh) when
making the change.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
actions/src/lib.rs (2)
39-72:⚠️ Potential issue | 🔴 CriticalAdd the missing
serde_with::serde_aswiring.Line 49 and Line 67 use field-level
#[serde_as(...)], butFunctionCallActionstill lacks the container-level#[serde_as], andserde_asis not imported. That leaves the serde-with configuration incomplete and should fail to compile.Suggested fix
use near_sdk::{ Gas, GasWeight, NearToken, Promise, borsh::{self, BorshSerialize}, near, serde::Serialize, serde_json, - serde_with::{DisplayFromStr, base64::Base64}, + serde_with::{serde_as, DisplayFromStr, base64::Base64}, }; ... #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde_as] #[near(serializers = [borsh, json])] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FunctionCallAction {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@actions/src/lib.rs` around lines 39 - 72, The struct FunctionCallAction uses field-level #[serde_as(...)] attributes (on args and gas_weight) but lacks the container-level #[serde_as] attribute and the serde_as import; fix by adding a container attribute #[serde_as] above the FunctionCallAction definition and add use serde_with::serde_as; (or equivalent) to imports so the field-level serde_with annotations (e.g., #[serde_as(as = "Base64")] on args and #[serde_as(as = "DisplayFromStr")] on gas_weight) compile correctly.
93-107:⚠️ Potential issue | 🟠 MajorDon’t panic on serialization errors in public builders.
serde_json::to_vecandborsh::to_vecare fallible, so theunwrap()calls on Line 98 and Line 106 makeargs_json()/args_borsh()panic on valid caller input. Please expose fallible variants (try_args_json/try_args_borsh) or returnResult<Self, _>here so callers can handle serialization failures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@actions/src/lib.rs` around lines 93 - 107, The current args_json and args_borsh builders call serde_json::to_vec(...).unwrap() and borsh::to_vec(...).unwrap(), which can panic; change these to fallible APIs: either add try_args_json and try_args_borsh that return Result<Self, E> (propagating the serialization error) or change args_json/args_borsh to return Result<Self, E> instead of Self; replace the unwrap() calls with proper error propagation (using ? or map_err) and update callers/tests accordingly so serialization failures are surfaced rather than panicking (refer to the args_json and args_borsh methods for the exact symbols to modify).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@actions/src/arbitrary_helpers.rs`:
- Around line 9-11: The gas() helper currently generates min_gas up to 300 TGas
independently, which can conflict with gas_weight applied later in
FunctionCallAction::append() via Promise::function_call_weight(); change the
approach so min_gas and gas_weight are generated together and validated:
implement a custom Arbitrary for FunctionCallAction (or a combined generator
used when constructing FunctionCallAction) that produces a consistent pair
(min_gas, gas_weight) by either capping min_gas to account for the extra gas
added by function_call_weight() or by sampling gas_weight first and then
restricting min_gas to a safe maximum (e.g., total_prepaid_limit -
function_call_weight(gas_weight)); update uses of gas() or replace it in
FunctionCallAction construction to use this coordinated generator so tests no
longer produce "Exceeded the prepaid gas." failures.
---
Duplicate comments:
In `@actions/src/lib.rs`:
- Around line 39-72: The struct FunctionCallAction uses field-level
#[serde_as(...)] attributes (on args and gas_weight) but lacks the
container-level #[serde_as] attribute and the serde_as import; fix by adding a
container attribute #[serde_as] above the FunctionCallAction definition and add
use serde_with::serde_as; (or equivalent) to imports so the field-level
serde_with annotations (e.g., #[serde_as(as = "Base64")] on args and
#[serde_as(as = "DisplayFromStr")] on gas_weight) compile correctly.
- Around line 93-107: The current args_json and args_borsh builders call
serde_json::to_vec(...).unwrap() and borsh::to_vec(...).unwrap(), which can
panic; change these to fallible APIs: either add try_args_json and
try_args_borsh that return Result<Self, E> (propagating the serialization error)
or change args_json/args_borsh to return Result<Self, E> instead of Self;
replace the unwrap() calls with proper error propagation (using ? or map_err)
and update callers/tests accordingly so serialization failures are surfaced
rather than panicking (refer to the args_json and args_borsh methods for the
exact symbols to modify).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d7e058a4-64ca-439a-9e23-dfec6dd8e404
📒 Files selected for processing (3)
actions/src/arbitrary_helpers.rsactions/src/lib.rsdefuse/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
- defuse/Cargo.toml
| pub fn gas(u: &mut Unstructured<'_>) -> Result<Gas> { | ||
| u.int_in_range(0..=Gas::from_tgas(300).as_gas()) | ||
| .map(Gas::from_gas) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.rs" | grep -E "(arbitrary_helpers|lib)" | head -20Repository: near/intents
Length of output: 510
🏁 Script executed:
ls -la actions/src/ 2>/dev/null || find . -type d -name "actions" | head -5Repository: near/intents
Length of output: 292
🏁 Script executed:
git ls-files | grep -E "actions.*\.rs$" | head -20Repository: near/intents
Length of output: 107
🏁 Script executed:
cat -n actions/src/arbitrary_helpers.rsRepository: near/intents
Length of output: 541
🏁 Script executed:
cat -n actions/src/lib.rsRepository: near/intents
Length of output: 5058
🏁 Script executed:
rg -i "function_call_weight|GasWeight" --type rs | head -40Repository: near/intents
Length of output: 82
🏁 Script executed:
# Search for any validation or constraint logic involving min_gas and gas_weight
rg "min_gas.*gas_weight|gas_weight.*min_gas" --type rs -A 3 -B 3Repository: near/intents
Length of output: 82
🏁 Script executed:
# Check if there are any tests or examples showing the expected constraints
fd -e rs -x grep -l "FunctionCallAction\|function_call_weight" {} \; | xargs grep -l "test\|example" 2>/dev/null | head -5Repository: near/intents
Length of output: 148
🏁 Script executed:
rg -i "function_call_weight" | head -20Repository: near/intents
Length of output: 270
🏁 Script executed:
rg "GasWeight" | head -20Repository: near/intents
Length of output: 607
🏁 Script executed:
# Check the test file that was found
cat -n ./tests/src/tests/defuse/arbitrary.rs | head -100Repository: near/intents
Length of output: 4062
🏁 Script executed:
# Check if there are any comments or documentation about gas constraints
rg "min_gas|gas_weight|function_call_weight" -A 5 -B 5 | grep -E "//|/\*|gas.*limit|overflow" | head -30Repository: near/intents
Length of output: 2720
🏁 Script executed:
# Look for Cargo.toml to see near_sdk version and dependencies
cat -n Cargo.toml 2>/dev/null || find . -maxdepth 2 -name "Cargo.toml" -exec cat -n {} \;Repository: near/intents
Length of output: 4956
🏁 Script executed:
# Check if there are any validation tests or constraints
rg "test.*gas|gas.*test" --type rust -A 10 | head -50Repository: near/intents
Length of output: 4166
🏁 Script executed:
# Look for any Arbitrary impl for FunctionCallAction to see if there's custom logic
rg "impl.*Arbitrary.*FunctionCallAction" -A 20Repository: near/intents
Length of output: 38
🏁 Script executed:
# Check if there's documentation or comments about gas_weight behavior
rg "gas_weight" -B 5 -A 5 | head -80Repository: near/intents
Length of output: 5419
🏁 Script executed:
# Search for any constraint validation between min_gas and gas_weight
rg "gas_weight.*0|if.*gas_weight" | head -20Repository: near/intents
Length of output: 1784
🏁 Script executed:
# Look at default_gas_weight to understand what values gas_weight takes
rg "default_gas_weight\|GasWeight::default" -B 2 -A 2Repository: near/intents
Length of output: 38
🏁 Script executed:
# Check what the default gas_weight value is
rg "GasWeight::default" -A 3Repository: near/intents
Length of output: 206
🏁 Script executed:
# Look for any validation or constraints on gas_weight in near_sdk or Promise calls
rg "with_static_gas|with_unused_gas_weight|function_call_weight" -B 2 -A 2 | head -60Repository: near/intents
Length of output: 4262
🏁 Script executed:
# Check if FunctionCallAction has any custom validation logic
rg "impl.*FunctionCallAction" -A 30Repository: near/intents
Length of output: 4884
🏁 Script executed:
# Search for any constraints or validation between min_gas and gas_weight
rg "min_gas.*gas_weight|gas_weight.*min_gas|GasWeight.*0|unused_gas_weight" -B 3 -A 3 | head -100Repository: near/intents
Length of output: 7841
🏁 Script executed:
# Look for any documentation or constraints about min_gas + gas_weight interaction
rg "GasWeight\|function_call_weight" -B 10 -A 5 | head -120Repository: near/intents
Length of output: 38
🏁 Script executed:
# Check if there's validation logic in tests that shows constraints
rg "min_gas.*300|300.*min_gas|gas_weight.*0|validate" | head -30Repository: near/intents
Length of output: 2694
🏁 Script executed:
# Look at the exact_gas method which sets both min_gas and gas_weight
rg "exact_gas" -B 2 -A 2Repository: near/intents
Length of output: 273
🏁 Script executed:
# Check if there are any comments about the relationship between these fields
rg "min_gas|gas_weight" -B 3 -A 3 | grep -E "//|/\*" | head -40Repository: near/intents
Length of output: 3109
🏁 Script executed:
# Search for the test file that mentions min_gas: Some(Gas::from_tgas(300))
cat -n tests/src/tests/defuse/intents/ft_withdraw.rs | grep -A 20 -B 5 "300"Repository: near/intents
Length of output: 2019
🏁 Script executed:
# Check what GasWeight::default() actually returns
rg "impl.*Default.*GasWeight|const.*GasWeight" | head -10Repository: near/intents
Length of output: 38
🏁 Script executed:
# Look at the Arbitrary derive on FunctionCallAction more carefully
cat -n actions/src/lib.rs | sed -n '39,73p'Repository: near/intents
Length of output: 1331
🏁 Script executed:
# Check the actual test error message and context around the 300 TGas failure
cat -n tests/src/tests/defuse/intents/ft_withdraw.rs | sed -n '130,160p'Repository: near/intents
Length of output: 1204
🏁 Script executed:
# Search for any comments or documentation about how min_gas and gas_weight should interact
rg "Arbitrary\|arbitrary\|fuzz\|property" --type rust -i | grep -i "gas\|weight" | head -20Repository: near/intents
Length of output: 38
🌐 Web query:
near_sdk Promise function_call_weight gas_weight semantics min_gas
💡 Result:
Promise::function_call_weight (near-sdk-rs) — gas, GasWeight, and “min_gas” semantics
-
Signature:
function_call_weight(function_name, args, amount, gas: Gas, weight: GasWeight)schedules a cross-contract call likefunction_call, but also assigns a weight used to distribute unused (leftover) gas from the current execution to outbound calls. [1] -
gasis the “minimum / base” gas attached to that call. You still provide an explicitGasvalue, and the protocol may later add extra gas on top from whatever remains unused when the current method finishes. So, conceptually:attached_gas_for_call = gas + (share_of_leftover_gas_by_weight)[1][2][3]
-
GasWeight(weight) semantics: leftover gas is split proportionally across all function calls scheduled with weights (in the same scheduling execution). Default weight is1. Example from docs: if 40 gas is leftover and weights are1, 5, 2, the leftover is split5, 25, 10. [2]
Nearcore describes this as specifying a ratio of unspent gas computed after the current call has finished (NEP-264 /promise_batch_action_function_call_weight). [3] -
What “
min_gas” usually refers to (common confusion):- On NEAR,
min_gas_priceis a protocol configuration parameter (a floor on gas price), returned by the RPC/protocol_configresponse—it’s not the same thing as thegasyou attach to a promise action. [4] - In many contracts/examples you may also see a variable named
min_gasmeaning “minimum gas required for this specific pattern” (e.g.,X + Yfor call+callback), but that’s an app-level convention, not a near-sdk builtin.
- On NEAR,
Sources: Promise docs [1], GasWeight docs [2], nearcore gas/NEP-264 explanation [3], NEAR RPC protocol config (min_gas_price) [4].
Coordinate arbitrary min_gas with gas_weight generation.
The gas() helper independently generates min_gas up to 300 TGas, but FunctionCallAction combines this with gas_weight (which defaults to nonzero) via Promise::function_call_weight() in the append() method. Since function_call_weight allocates additional gas based on the weight, generating maximum min_gas with the default nonzero gas_weight creates internally inconsistent arbitrary values that fail execution for generator reasons rather than exercising meaningful application states. This is evidenced by tests showing 300 TGas failing with "Exceeded the prepaid gas." errors.
Either implement a custom Arbitrary impl for FunctionCallAction that validates min_gas and gas_weight as a coordinated pair, or generate these fields together with constraints that prevent invalid combinations.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@actions/src/arbitrary_helpers.rs` around lines 9 - 11, The gas() helper
currently generates min_gas up to 300 TGas independently, which can conflict
with gas_weight applied later in FunctionCallAction::append() via
Promise::function_call_weight(); change the approach so min_gas and gas_weight
are generated together and validated: implement a custom Arbitrary for
FunctionCallAction (or a combined generator used when constructing
FunctionCallAction) that produces a consistent pair (min_gas, gas_weight) by
either capping min_gas to account for the extra gas added by
function_call_weight() or by sampling gas_weight first and then restricting
min_gas to a safe maximum (e.g., total_prepaid_limit -
function_call_weight(gas_weight)); update uses of gas() or replace it in
FunctionCallAction construction to use this coordinated generator so tests no
longer produce "Exceeded the prepaid gas." failures.
Summary by CodeRabbit
New Features
defuse-actionspackage with action abstractions for transfers and function calls.arbitrary_callcontract method to execute actions with DAO role-based access control and deposit validation.Tests