gtest has always been the fast path for testing Gear programs locally. You can upload a program, send messages, move blocks forward, and check logs without running a node. That makes it the right place to catch most program bugs before they become integration bugs. But ethexe changes some important rules. The execution layer still runs Gear programs, but it does not look exactly like Vara. It has canonical and injected message queues, Ethereum-facing execution constraints, and a reverse gas model where programs spend from their executable balance. A test that only runs under regular gtest can still miss behavior that matters once the same program is used through ethexe. That is the gap we wanted to close.
The goal
The goal was simple: make ethexe behavior testable from gtest. Not by replacing the existing mode. Not by making every old test learn new rules. And not by dragging the full ethexe processor into the SDK test crate. Instead, gtest gets an opt-in ethexe mode:
let system = gtest::System::new_ethexe();
System::new() keeps the normal Vara-style behavior. System::new_ethexe() creates a test system that follows ethexe-shaped execution rules. That split matters. Existing tests stay stable, while developers who need ethexe semantics can ask for them directly.
What had to change
The main change is that gtest needs to own enough ethexe state to execute programs like ethexe does. In regular gtest, a message looks like a Vara message: the caller provides gas, the message goes through the normal queue, and the result is reported back through the familiar BlockRunResult. In ethexe mode, the model is different:
- canonical messages go into a program's canonical queue;
- injected messages go into a separate injected queue;
- injected messages are processed before canonical messages;
- explicit gas-limit sends are rejected, because ethexe does not use Vara's user-funded gas model;
- execution gas comes from the program's executable balance;
- block allowance is charged the way ethexe charges it, including chunked execution where the block pays for the maximum gas spent in a chunk rather than summing every program in that chunk. That is a lot of behavior to model, but the boundary is deliberately small. gtest can depend on shared ethexe types from ethexe-common and ethexe-runtime-common, but it does not depend on ethexe-processor. That keeps the SDK test layer light. It also prevents the test helper from becoming a second entry point into the production processor.
Reverse gas becomes testable
The biggest user-facing win is reverse gas. On Vara, a user sends a message with gas. In ethexe, the program has an executable balance, and execution burns from that balance. That changes how developers should think about program availability. A program can have funds for normal value transfers and still fail execution if its executable balance is too low. With ethexe mode in gtest, tests can express that directly: let system = gtest::System::new_ethexe(); let program = Program::current(&system); system.top_up_executable_balance(program.id(), 1_000_000); program.send_bytes(42, b"PING"); let result = system.run_next_block(); assert!(result.succeed.len() > 0); assert!(program.executable_balance() < 1_000_000); The exact assertions depend on the program, but the important part is that executable balance is now part of the local test surface. Developers can test funded execution, low-balance execution, panic charging, and gas accounting without setting up Ethereum infrastructure.
Injected messages become testable too
Ethexe has another path that regular gtest did not model: injected messages. Injected messages are not the same as normal user sends. They come from the ethexe side of the system and are handled before canonical messages. That ordering can matter. If a program receives both an injected transaction and a normal message in the same block, the injected queue wins. The ethexe mode adds an explicit API for that: system.inject_message(destination, source, payload, value); There is one important rule: injection into an uninitialized program is rejected. That matches ethexe processor behavior and prevents tests from accidentally relying on a state that cannot happen in production. For developers, this means cross-chain and Ethereum-facing flows can be tested closer to the way they actually execute.
Why not just use the ethexe processor?
It would be tempting to make gtest depend on ethexe-processor and call it a day. That would make the first version easier, but it would make the long-term boundary worse. gtest is a developer testing tool. It should stay quick, focused, and easy to use from program tests. The processor is production infrastructure with a broader job: prepare code, execute blocks, coordinate with runtime state, and fit into the ethexe service pipeline. The better boundary is to share the runtime-common pieces that define ethexe execution behavior, then let gtest build a small local backend around them. That gives us the behavior we need without coupling the SDK test crate to the production processor.
What developers get
For Gear program developers, this means fewer surprises. You can keep writing normal gtest tests for Vara-style behavior. When your program needs to run under ethexe rules, switch the system constructor and test those rules locally. That gives you coverage for things like:
- executable balance top-ups;
- reverse gas charging;
- low executable balance failures;
- canonical versus injected message ordering;
- rejected Vara-only APIs in ethexe mode;
- ethexe panic charging behavior;
- block allowance accounting across multiple programs. For SDK and runtime developers, the benefit is also practical. The semantics now live behind tests that can run without a node, without Anvil, and without a full ethexe service. That makes regressions cheaper to catch and easier to understand.
The shape of the result
The final shape is intentionally boring:
- System::new() keeps existing behavior.
- System::new_ethexe() opts into ethexe behavior.
- Program::send* queues canonical messages in ethexe mode.
- System::inject_message(...) queues injected messages.
- System::top_up_executable_balance(...) funds execution.
- Program::executable_balance() exposes the remaining execution pool.
- explicit gas-limit send APIs fail early in ethexe mode. That is the kind of API split we want in a test framework. The default path stays familiar. The ethexe path is explicit. The difference is visible in the test code.
Why it matters
Ethexe brings Gear programs closer to Ethereum, but that only helps developers if the local testing story follows along. Putting ethexe semantics in gtest means developers can test the important differences where they already test everything else: in fast Rust tests, close to the program code, with small fixtures and direct assertions. That shortens the loop. It also makes the model clearer. Reverse gas is no longer something you only discover in integration tests. Injected message ordering is no longer hidden behind service setup. Ethexe-only restrictions are no longer late surprises. The result is simple: programs that target ethexe can be tested like ethexe programs earlier, faster, and with less machinery.
Vara
Website | X | Discord | Telegram | Wiki | GitHub
Gear Protocol
Website | X | Discord | Telegram | GitHub | Gear IDEA | Whitepaper
