The validate_delegate_action function in runtime/runtime/src/verifier.rs:685-694 passes the outer transaction's receiver (which is the sender's own account in the meta-transaction pattern) instead of delegate_action.receiver_id when validating inner actions. This causes validate_deterministic_state_init to compare the derived deterministic account ID against the sender's account name, which always fails because deterministic IDs (e.g., 0sb057cd...) and regular account names (e.g., alice.near) use incompatible formats.
The result is that ALL DeterministicStateInit actions wrapped in delegate actions (meta-transactions / NEP-366) are rejected at the transaction validation stage. The feature works correctly via direct transactions but is completely broken for the gasless/relayer use case that meta-transactions are designed to enable.
Both features are live on mainnet: DeterministicAccountIds (NEP-616) at protocol version 82, delegate actions (NEP-366) at protocol version 63. Mainnet is currently at protocol version 85.
runtime/runtime/src/verifier.rs:685-694fn validate_delegate_action(
limit_config: &LimitConfig,
signed_delegate_action: &SignedDelegateAction,
receiver: &AccountId, // <-- THIS IS THE OUTER TX RECEIVER, NOT delegate_action.receiver_id
current_protocol_version: ProtocolVersion,
) -> Result<(), ActionsValidationError> {
let actions = signed_delegate_action.delegate_action.get_actions();
validate_actions(limit_config, &actions, receiver, current_protocol_version)?;
// ^^^^^^^^ BUG: passes wrong receiver
Ok(())
}
verifier.rs:101-113fn validate_transaction_actions(
config: &RuntimeConfig,
signed_tx: &SignedTransaction,
current_protocol_version: ProtocolVersion,
) -> Result<(), InvalidTxError> {
validate_actions(
&config.wasm_config.limit_config,
signed_tx.transaction.actions(),
signed_tx.transaction.receiver_id(), // <-- For meta-tx: this is the sender's account
current_protocol_version,
)
.map_err(InvalidTxError::ActionsValidation)
}
verifier.rs:893-914fn validate_deterministic_state_init(
// ...
receiver_id: &AccountId,
// ...
) -> Result<(), ActionsValidationError> {
let derived_id = derive_near_deterministic_account_id(&action.state_init);
if derived_id != *receiver_id { // <-- Compares "0sb057cd..." against "account0"
return Err(ActionsValidationError::InvalidDeterministicStateInitReceiver {
derived_id,
receiver_id: receiver_id.clone(),
});
}
// ...
}
When DeterministicStateInit is used via a direct transaction (not a delegate action), the transaction's receiver_id IS the deterministic account ID, so the comparison succeeds. The bug only manifests in the delegate action path where the outer transaction receiver diverges from the inner action's target.
High (DoS fixable without hardfork)
This is NOT a network-level DoS. This is a protocol-level validation bug that prevents a specific combination of two protocol features (NEP-366 + NEP-616) from working together. The fix is a one-line code change with a protocol version bump. It does not require a hardfork.
Per NEAR's bounty program: "DoS issues which may be fixed without hardfork are accepted as High severity issue with fixed bounty payout of $10,000."
This maps to the in-scope class "Contracts execution flows": the NEAR runtime incorrectly rejects valid transactions that combine delegate actions with DeterministicStateInit. Any dApp or service that relies on meta-transactions (relayers) for gasless creation of deterministic accounts (NEP-616) is broken.
InvalidDeterministicStateInitReceiver), making it difficult for developers to diagnose.Pass delegate_action.receiver_id instead of the outer receiver when validating inner actions:
diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs
--- a/runtime/runtime/src/verifier.rs
+++ b/runtime/runtime/src/verifier.rs
@@ -685,10 +685,14 @@
fn validate_delegate_action(
limit_config: &LimitConfig,
signed_delegate_action: &SignedDelegateAction,
receiver: &AccountId,
current_protocol_version: ProtocolVersion,
) -> Result<(), ActionsValidationError> {
let actions = signed_delegate_action.delegate_action.get_actions();
- validate_actions(limit_config, &actions, receiver, current_protocol_version)?;
+ let inner_receiver = if ProtocolFeature::FixDelegateActionInnerReceiverValidation.enabled(current_protocol_version) {
+ &signed_delegate_action.delegate_action.receiver_id
+ } else {
+ receiver
+ };
+ validate_actions(limit_config, &actions, inner_receiver, current_protocol_version)?;
Ok(())
}
The fix must be gated behind a new protocol feature to maintain consensus compatibility. Only DeterministicStateInit uses the receiver parameter for content validation among all action types, so this change has no effect on any other action type.
runtime/runtime/src/verifier.rs:685-694 (commit 8f7344610)verifier.rs:101-113verifier.rs:907-913Test-loop integration test running the REAL nearcore runtime with 4 validators, real consensus, and real transaction execution. The test demonstrates:
InvalidDeterministicStateInitReceiver (the bug)derived_id against the sender's account (account0) instead of the delegate action's receiver (0sb057cd...)nearcore/test-loop-tests/src/tests/poc_deterministic_delegate_bug.rs
Register in test-loop-tests/src/tests/mod.rs:
mod poc_deterministic_delegate_bug;
cd nearcore
cargo test -p test-loop-tests --features test_features -- poc_deterministic --nocapture
=== STEP 1: Deploying global contract ===
Global contract deployed on 'account'
=== STEP 2: DeterministicStateInit via DIRECT transaction ===
Derived deterministic account ID: 0s<derived_id_1>
DIRECT tx result: SUCCESS
signer (sender): account0
receiver: 0s<derived_id_1>
validation checks: derived_id == receiver
-> '0s<derived_id_1>' == '0s<derived_id_1>' => PASS
=== STEP 3: DeterministicStateInit via DELEGATE action (meta-tx) ===
Derived deterministic account ID: 0sb057cd297024029d2f7ca26463470048bd700f3d
Outer tx: signer=account2, receiver=account0
DelegateAction: sender_id=account0, receiver_id=0sb057cd297024029d2f7ca26463470048bd700f3d
Inner action: DeterministicStateInit -> derived_id=0sb057cd297024029d2f7ca26463470048bd700f3d
=== STEP 4: Bug Analysis ===
DELEGATE tx result: FAILED (as expected due to bug)
Error: InvalidDeterministicStateInitReceiver
derived_id = 0sb057cd297024029d2f7ca26463470048bd700f3d
receiver_id = account0
THE BUG:
validate_delegate_action() passes the OUTER tx receiver
to validate_actions(), not delegate_action.receiver_id.
Outer tx receiver (wrong): account0
delegate_action.receiver_id (correct): 0sb057cd297024029d2f7ca26463470048bd700f3d
Validation compared:
derived_id '0sb057cd297024029d2f7ca26463470048bd700f3d' == receiver 'account0'
But it SHOULD have compared:
derived_id '0sb057cd297024029d2f7ca26463470048bd700f3d' == delegate_action.receiver_id '0sb057cd297024029d2f7ca26463470048bd700f3d'
BUG CONFIRMED: DeterministicStateInit is unusable via meta-transactions.
=== PoC Complete ===
test tests::poc_deterministic_delegate_bug::poc_deterministic_state_init_delegate_action_fails ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 366 filtered out; finished in 3.66s