Skip to content

Commit ea95bd5

Browse files
authored
fix(rpc): wrongly evaluating skip fee flag (#65)
1 parent 4df1efc commit ea95bd5

File tree

2 files changed

+160
-2
lines changed

2 files changed

+160
-2
lines changed

crates/rpc/rpc/src/starknet/trace.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@ impl<EF: ExecutorFactory> StarknetApi<EF> {
6969

7070
// If the node is run with fee charge disabled, then we should disable charing fees even
7171
// if the `SKIP_FEE_CHARGE` flag is not set.
72-
let should_skip_fee = !simulation_flags.contains(&SimulationFlag::SkipFeeCharge)
72+
let should_charge_fee = !simulation_flags.contains(&SimulationFlag::SkipFeeCharge)
7373
&& self.inner.backend.executor_factory.execution_flags().fee();
7474

7575
let flags = katana_executor::ExecutionFlags::new()
7676
.with_account_validation(should_validate)
77-
.with_fee(!should_skip_fee)
77+
.with_fee(should_charge_fee)
7878
.with_nonce_check(false);
7979

8080
// get the state and block env at the specified block for execution

crates/rpc/rpc/tests/simulate.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use cainome::rs::abigen_legacy;
2+
use katana_primitives::genesis::constant::{
3+
DEFAULT_ETH_FEE_TOKEN_ADDRESS, DEFAULT_PREFUNDED_ACCOUNT_BALANCE,
4+
};
5+
use katana_utils::TestNode;
6+
use starknet::accounts::{Account, ExecutionEncoding, SingleOwnerAccount};
7+
use starknet::core::types::{BlockId, BlockTag, Felt};
8+
use starknet::macros::felt;
9+
use starknet::providers::Provider;
10+
use starknet::signers::{LocalWallet, SigningKey};
11+
12+
mod common;
13+
14+
abigen_legacy!(Erc20Contract, "crates/rpc/rpc/tests/test_data/erc20.json", derives(Clone));
15+
16+
#[tokio::test]
17+
async fn simulate() {
18+
let sequencer = TestNode::new().await;
19+
let account = sequencer.account();
20+
21+
let contract = Erc20Contract::new(DEFAULT_ETH_FEE_TOKEN_ADDRESS.into(), &account);
22+
23+
let recipient = felt!("0x1");
24+
let amount = Uint256 { low: felt!("0x1"), high: Felt::ZERO };
25+
26+
let result = contract.transfer(&recipient, &amount).simulate(false, false).await;
27+
assert!(result.is_ok(), "simulate should succeed");
28+
}
29+
30+
#[rstest::rstest]
31+
#[tokio::test]
32+
async fn simulate_nonce_validation(#[values(None, Some(1000))] block_time: Option<u64>) {
33+
// setup test sequencer with the given configuration
34+
let mut config = katana_utils::node::test_config();
35+
config.sequencing.block_time = block_time;
36+
let sequencer = TestNode::new_with_config(config).await;
37+
let provider = sequencer.starknet_provider();
38+
let account = sequencer.account();
39+
40+
let contract = Erc20Contract::new(DEFAULT_ETH_FEE_TOKEN_ADDRESS.into(), &account);
41+
42+
let recipient = felt!("0x1");
43+
let amount = Uint256 { low: felt!("0x1"), high: Felt::ZERO };
44+
45+
// send a valid transaction first to increment the nonce (so that we can test nonce < current
46+
// nonce later)
47+
let res = contract.transfer(&recipient, &amount).send().await.unwrap();
48+
dojo_utils::TransactionWaiter::new(res.transaction_hash, &provider).await.unwrap();
49+
50+
// simulate with current nonce (the expected nonce)
51+
let nonce =
52+
provider.get_nonce(BlockId::Tag(BlockTag::Pending), account.address()).await.unwrap();
53+
let result = contract.transfer(&recipient, &amount).nonce(nonce).simulate(false, false).await;
54+
assert!(result.is_ok(), "estimate should succeed with nonce == current nonce");
55+
56+
// simulate with arbitrary nonce < current nonce
57+
//
58+
// here we're essentially simulating a transaction with a nonce that has already been
59+
// used, so it should fail.
60+
let nonce = nonce - 1;
61+
let result = contract.transfer(&recipient, &amount).nonce(nonce).simulate(false, false).await;
62+
assert!(result.is_err(), "estimate should fail with nonce < current nonce");
63+
64+
// simulate with arbitrary nonce >= current nonce
65+
let nonce = felt!("0x1337");
66+
let result = contract.transfer(&recipient, &amount).nonce(nonce).simulate(false, false).await;
67+
assert!(result.is_ok(), "estimate should succeed with nonce >= current nonce");
68+
}
69+
70+
#[rstest::rstest]
71+
#[tokio::test]
72+
async fn simulate_with_insufficient_fee(
73+
#[values(true, false)] disable_node_fee: bool,
74+
#[values(None, Some(1000))] block_time: Option<u64>,
75+
) {
76+
// setup test sequencer with the given configuration
77+
let mut config = katana_utils::node::test_config();
78+
config.dev.fee = !disable_node_fee;
79+
config.sequencing.block_time = block_time;
80+
let sequencer = TestNode::new_with_config(config).await;
81+
82+
let contract = Erc20Contract::new(DEFAULT_ETH_FEE_TOKEN_ADDRESS.into(), sequencer.account());
83+
84+
let recipient = Felt::ONE;
85+
let amount = Uint256 { low: Felt::ONE, high: Felt::ZERO };
86+
87+
// -----------------------------------------------------------------------
88+
// transaction with low max fee (underpriced).
89+
90+
let fee = Felt::TWO;
91+
let res = contract.transfer(&recipient, &amount).max_fee(fee).simulate(false, false).await;
92+
93+
if disable_node_fee {
94+
assert!(res.is_ok(), "should succeed when fee is disabled");
95+
} else {
96+
assert!(res.is_err(), "should fail when fee is enabled");
97+
}
98+
99+
// simulate with 'skip fee charge' flag
100+
let result = contract.transfer(&recipient, &amount).max_fee(fee).simulate(false, true).await;
101+
assert!(result.is_ok(), "should succeed no matter");
102+
103+
// -----------------------------------------------------------------------
104+
// transaction with insufficient balance.
105+
106+
let fee = Felt::from(DEFAULT_PREFUNDED_ACCOUNT_BALANCE + 1);
107+
let result = contract.transfer(&recipient, &amount).max_fee(fee).simulate(false, false).await;
108+
109+
if disable_node_fee {
110+
assert!(result.is_ok(), "estimate should succeed when fee is disabled");
111+
} else {
112+
assert!(res.is_err(), "should fail when fee is enabled");
113+
}
114+
115+
// simulate with 'skip fee charge' flag
116+
let result = contract.transfer(&recipient, &amount).max_fee(fee).simulate(false, true).await;
117+
assert!(result.is_ok(), "should succeed no matter");
118+
}
119+
120+
#[rstest::rstest]
121+
#[tokio::test]
122+
async fn simulate_with_invalid_signature(
123+
#[values(true, false)] disable_node_validate: bool,
124+
#[values(None, Some(1000))] block_time: Option<u64>,
125+
) {
126+
// setup test sequencer with the given configuration
127+
let mut config = katana_utils::node::test_config();
128+
config.dev.account_validation = !disable_node_validate;
129+
config.sequencing.block_time = block_time;
130+
let sequencer = TestNode::new_with_config(config).await;
131+
132+
// starknet-rs doesn't provide a way to manually set the signatures so instead we create an
133+
// account with random signer to simulate invalid signatures.
134+
135+
let account = SingleOwnerAccount::new(
136+
sequencer.starknet_provider(),
137+
LocalWallet::from(SigningKey::from_random()),
138+
sequencer.account().address(),
139+
sequencer.starknet_provider().chain_id().await.unwrap(),
140+
ExecutionEncoding::New,
141+
);
142+
143+
let contract = Erc20Contract::new(DEFAULT_ETH_FEE_TOKEN_ADDRESS.into(), &account);
144+
145+
let recipient = Felt::ONE;
146+
let amount = Uint256 { low: Felt::ONE, high: Felt::ZERO };
147+
let result = contract.transfer(&recipient, &amount).simulate(false, false).await;
148+
149+
if disable_node_validate {
150+
assert!(result.is_ok(), "should succeed when validate is disabled");
151+
} else {
152+
assert!(result.is_err(), "should fail when validate is enabled");
153+
}
154+
155+
// simulate with 'skip validate' flag
156+
let result = contract.transfer(&recipient, &amount).simulate(true, false).await;
157+
assert!(result.is_ok(), "should succeed no matter");
158+
}

0 commit comments

Comments
 (0)