Skip to content

Tx Filtering additional system tests#4503

Draft
andreroc wants to merge 1 commit intomasterfrom
nit-4447-additional-tx-system-tests
Draft

Tx Filtering additional system tests#4503
andreroc wants to merge 1 commit intomasterfrom
nit-4447-additional-tx-system-tests

Conversation

@andreroc
Copy link
Copy Markdown
Contributor

I initially wanted to add a single test for a contract that uses a contract as an intermediary before paying out to a censored address. I then asked Claude to find missing tests and it added several more.

Claude's output:
Here's a summary of everything implemented:

Changes

Solidity: contracts-local/src/mocks/AddressFilterTest.sol

Added 3 new functions:

  • delegatecallTarget(address) — DELEGATECALL to target
  • callcodeTarget(address) — CALLCODE to target (inline assembly)
  • callVia(address intermediary, address target) — Two-hop CALL chain: this → intermediary.callTarget(target)

Tests: system_tests/tx_address_filter_test.go

Added 5 new test functions:

  1. TestAddressFilterDelegateCall — Verifies that DELEGATECALL to a filtered address does not trigger filtering. This is correct behavior because DELEGATECALL
    only loads code from the target; the execution context stays in the caller. PushContract touches contract.Address() which is the caller, not the code source.
    Includes a sanity check that a regular CALL to the same address IS still filtered.
  2. TestAddressFilterCallCode — Same as above for CALLCODE. Verifies the target address (code source only) is not filtered.
  3. TestAddressFilterCallViaFilteredIntermediary — Tests a call chain where the intermediary is filtered (not the final target). Payer → filtered-intermediary
    → clean-target should fail because entering the intermediary via CALL touches its address in PushContract.
  4. TestAddressFilterContractDeploy — Tests top-level contract deployment (EOA sends a tx with no to field) where the resulting contract address is filtered.
    Pre-computes the CREATE address from deployer nonce, filters it, then verifies deployment is rejected.
  5. TestAddressFilterEventBypassRule — Tests the BypassRule in event filtering. Sets up a Transfer event filter with a bypass rule: skip filtering when from
    topic equals a specific address. Verifies: (a) transfer TO filtered address is normally rejected, (b) transfer FROM the bypass address TO filtered address
    succeeds, (c) transfer FROM filtered address (non-bypass) is still rejected.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 29.13%. Comparing base (9927906) to head (cbf49bf).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4503      +/-   ##
==========================================
- Coverage   32.79%   29.13%   -3.66%     
==========================================
  Files         494      495       +1     
  Lines       58611    58648      +37     
==========================================
- Hits        19223    17090    -2133     
- Misses      36026    38457    +2431     
+ Partials     3362     3101     -261     

@github-actions
Copy link
Copy Markdown
Contributor

❌ 12 Tests Failed:

Tests completed Failed Passed Skipped
4388 12 4376 0
View the top 3 failed tests by shortest run time
TestAddressFilterIndirectPayment
Stack Traces | 0.070s run time
=== RUN   TestAddressFilterIndirectPayment
=== PAUSE TestAddressFilterIndirectPayment
=== CONT  TestAddressFilterIndirectPayment
    tx_address_filter_test.go:431: expected filtered error, got: execution reverted: payVia failed
--- FAIL: TestAddressFilterIndirectPayment (0.07s)
TestAddressFilterIndirectPayment
Stack Traces | 0.100s run time
=== RUN   TestAddressFilterIndirectPayment
=== PAUSE TestAddressFilterIndirectPayment
=== CONT  TestAddressFilterIndirectPayment
    tx_address_filter_test.go:431: expected filtered error, got: execution reverted: payVia failed
--- FAIL: TestAddressFilterIndirectPayment (0.10s)
TestAddressFilterIndirectPayment
Stack Traces | 0.280s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
DEBUG[03-12|17:47:04.847] Served eth_call                          reqid=1486 duration="386.058µs"
DEBUG[03-12|17:47:04.848] Served eth_getBalance                    reqid=1948 duration="521.471µs"
DEBUG[03-12|17:47:04.848] Pushed sync data from consensus to execution synced=true  maxMessageCount=1   updatedAt=2026-03-12T17:47:04+0000 hasProgressMap=false
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1486 duration="1.854µs"
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1695 duration="1.623µs"
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1948 duration="1.283µs"
INFO [03-12|17:47:04.848] Imported new potential chain segment     number=217 hash=b56c46..c2b24e                   blocks=1  txs=1   mgas=0.021 elapsed=1.256ms         mgasps=16.714   triediffs=565.01KiB triedirty=77.23KiB
TRACE[03-12|17:47:04.848] Engine API request received              method=ForkchoiceUpdated             head=b56c46..c2b24e finalized=48a79a..36dac3 safe=b56c46..c2b24e
DEBUG[03-12|17:47:04.848] Served eth_getBlockByNumber              reqid=1949 duration="103.754µs"
TRACE[03-12|17:47:04.847] Handled RPC response                     reqid=1360 duration="2.054µs"
DEBUG[03-12|17:47:04.848] Served eth_getTransactionCount           reqid=1950 duration="54.531µs"
TRACE[03-12|17:47:04.846] Handled RPC response                     reqid=1693 duration="2.755µs"
TRACE[03-12|17:47:04.847] Handled RPC response                     reqid=2362 duration="2.154µs"
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1691 duration="2.264µs"
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1949 duration="1.623µs"
TRACE[03-12|17:47:04.848] Handled RPC response                     reqid=1950 duration="1.403µs"
--- FAIL: TestAddressFilterIndirectPayment (0.28s)
DEBUG[03-12|17:47:04.885] Dereferenced trie from memory database   nodes=0     size=0.00B     time="1.112µs"    gcnodes=0   gcsize=0.00B    gctime="99.987µs"  livenodes=3562 livesize=693.46KiB
DEBUG[03-12|17:47:04.886] Executing EVM call finished              runtime="292.295µs"
DEBUG[03-12|17:47:04.885] Dereferenced trie from memory database   nodes=0     size=0.00B     time=962ns        gcnodes=0   gcsize=0.00B    gctime="11µs"      livenodes=34   livesize=5.86KiB

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

Copy link
Copy Markdown
Contributor

@MishkaRogachev MishkaRogachev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions and it's worth merging latest master

}

/// @notice Makes a CALLCODE to the target address (requires inline assembly)
function callcodeTarget(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as i know, CALLCODE is considered deprecated, I'm not sure we need to support it

// TestAddressFilterCallViaFilteredIntermediary verifies that when a non-filtered
// contract CALLs a filtered intermediary, the transaction is rejected even though
// the final target is not filtered.
func TestAddressFilterCallViaFilteredIntermediary(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seem to be similar to existing TestAddressFilterCall scenario

receive() external payable {}

/// @notice Send this contract's entire balance to the target address
function payTo(address payable target) external {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't payTo be payable?

Require(t, err)
}

func TestAddressFilterIndirectPayment(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test seems very valuable, but it fails now. payTo should be payable — right now the intermediary reverts before the filtered address is ever reached. There could also be a deeper gap in the filter (value transfers to filtered EOAs via internal CALLs may not trigger TouchAddress), but probably worth merging latest master first to investigate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants