From f0e14f41087fa89716145df20c62623fa6db5278 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 08:04:01 +0000 Subject: [PATCH] fix(taiko-client): run the integration tests against the PR's own protocol The integration tests deployed `taikoxyz/taiko-mono@main`'s contracts from a second checkout rather than the contracts in the pull request, so a change to the protocol could never be validated against the client in the same PR: the job tested different contracts on every re-run, and a protocol change that breaks the client only surfaced after it had merged. The taiko-client-rs workflow already deploys the PR's own `packages/protocol` for exactly this reason; do the same here, and trigger both workflows on the contracts and deploy script the tests actually run through `DeployProtocolOnL1`. Cover both ends of the `basefeeSharingPctg` range while we are here. The inbox accepts 0 to 100 inclusive, and taiko-geth splits a transaction's basefee as `gasUsed * baseFee * pctg / 100` to the coinbase with the remainder to the treasury, so 0 pays the coinbase nothing and 100 pays the treasury nothing. The client only ever plumbs the byte -- it does no arithmetic with it -- so the encoder is where the range is pinned: `EncodeShastaExtraData` had no test at all and now round-trips every boundary value through the decoder both clients rely on, asserts the percentage and proposal ID never read each other's bytes, and pins the three proposal IDs the encoder refuses. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UhqmeYDadT95CmcUhWzc2B --- .github/workflows/taiko-client--test.yml | 22 ++++--- .github/workflows/taiko-client-rs--test.yml | 6 ++ .../bindings/encoding/input_test.go | 65 +++++++++++++++++++ 3 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 packages/taiko-client/bindings/encoding/input_test.go diff --git a/.github/workflows/taiko-client--test.yml b/.github/workflows/taiko-client--test.yml index d827c943bbd..4c7d4338c1d 100644 --- a/.github/workflows/taiko-client--test.yml +++ b/.github/workflows/taiko-client--test.yml @@ -5,10 +5,16 @@ on: branches: [main] paths: - "packages/taiko-client/**" + - "packages/protocol/contracts/**" + - "packages/protocol/script/layer1/core/**" pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - "packages/taiko-client/**" + # The integration tests deploy these through `DeployProtocolOnL1`, so a change + # to them can break the client without touching a single client file. + - "packages/protocol/contracts/**" + - "packages/protocol/script/layer1/core/**" - "go.mod" - "go.sum" - "!**/*.md" @@ -85,7 +91,6 @@ jobs: timeout-minutes: 30 env: CGO_ENABLED: 1 - PROTOCOL_FORK_DIR: protocol-taiko-mono strategy: fail-fast: false @@ -100,13 +105,6 @@ jobs: - uses: actions/checkout@v7 - - name: Checkout protocol for testing - uses: actions/checkout@v7 - with: - repository: taikoxyz/taiko-mono - path: ${{ env.PROTOCOL_FORK_DIR }} - ref: main - - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -159,14 +157,18 @@ jobs: with: cache-dependency-path: pnpm-lock.yaml + # Deploy the PR's own protocol contracts: a second checkout pinned to `main` + # would test different contracts on every re-run and never exercise a PR + # that changes protocol and client together. - name: Install pnpm dependencies for protocol - run: cd ./${{ env.PROTOCOL_FORK_DIR }}/packages/protocol && pnpm install + working-directory: packages/protocol + run: pnpm install - name: Run Tests on ${{ matrix.execution_node }} execution engine working-directory: packages/taiko-client env: L2_NODE: ${{ matrix.execution_node }} - PROTOCOL_DIR: ${{ github.workspace }}/${{ env.PROTOCOL_FORK_DIR }}/packages/protocol + PROTOCOL_DIR: ${{ github.workspace }}/packages/protocol run: make test - name: Codecov.io diff --git a/.github/workflows/taiko-client-rs--test.yml b/.github/workflows/taiko-client-rs--test.yml index 6db5a4b0f93..41afa484773 100644 --- a/.github/workflows/taiko-client-rs--test.yml +++ b/.github/workflows/taiko-client-rs--test.yml @@ -7,10 +7,16 @@ on: branches: [main] paths: - "packages/taiko-client-rs/**" + - "packages/protocol/contracts/**" + - "packages/protocol/script/layer1/core/**" pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - "packages/taiko-client-rs/**" + # The integration tests deploy these through `DeployProtocolOnL1`, so a change + # to them can break the client without touching a single client file. + - "packages/protocol/contracts/**" + - "packages/protocol/script/layer1/core/**" - "!**/*.md" concurrency: diff --git a/packages/taiko-client/bindings/encoding/input_test.go b/packages/taiko-client/bindings/encoding/input_test.go new file mode 100644 index 00000000000..aa933935954 --- /dev/null +++ b/packages/taiko-client/bindings/encoding/input_test.go @@ -0,0 +1,65 @@ +package encoding + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +// basefeeSharingPctgRange covers every value the inbox accepts, both ends included: +// `LibInboxSetup.validateConfig` requires `basefeeSharingPctg <= 100`, and taiko-geth +// splits a transaction's basefee as `gasUsed * baseFee * pctg / 100` to the coinbase +// with the remainder to the treasury. At 0 the coinbase is paid nothing, at 100 the +// treasury is. +var basefeeSharingPctgRange = []uint8{0, 1, 25, 75, 99, 100} + +// TestEncodeShastaExtraDataBasefeeSharingPctg round-trips the percentage byte through +// the decoder both clients rely on, so a change to either end of the range shows up here +// rather than as a block the drivers re-derive with different extraData. +func TestEncodeShastaExtraDataBasefeeSharingPctg(t *testing.T) { + for _, pctg := range basefeeSharingPctgRange { + extraData, err := EncodeShastaExtraData(pctg, big.NewInt(1)) + require.Nil(t, err) + require.Len(t, extraData, params.ShastaExtraDataLen) + require.Equal(t, pctg, core.DecodeShastaBasefeeSharingPctg(extraData)) + } +} + +// TestEncodeShastaExtraDataIndependentFields asserts the percentage and the proposal ID +// never read each other's bytes, including when one of them is zero. +func TestEncodeShastaExtraDataIndependentFields(t *testing.T) { + maxProposalID := new(big.Int).Sub( + new(big.Int).Lsh(big.NewInt(1), uint(params.ShastaExtraDataProposalIDLength*8)), + big.NewInt(1), + ) + + for _, pctg := range basefeeSharingPctgRange { + for _, proposalID := range []*big.Int{big.NewInt(0), big.NewInt(1), big.NewInt(1337), maxProposalID} { + extraData, err := EncodeShastaExtraData(pctg, proposalID) + require.Nil(t, err) + + require.Equal(t, pctg, core.DecodeShastaBasefeeSharingPctg(extraData)) + + decodedID, err := core.DecodeShastaProposalID(extraData) + require.Nil(t, err) + require.Zero(t, proposalID.Cmp(decodedID)) + } + } +} + +// TestEncodeShastaExtraDataRejectsInvalidProposalID pins the three cases the encoder +// refuses, so an out-of-range proposal ID never silently truncates into the percentage byte. +func TestEncodeShastaExtraDataRejectsInvalidProposalID(t *testing.T) { + _, err := EncodeShastaExtraData(100, nil) + require.ErrorContains(t, err, "proposal ID is nil") + + _, err = EncodeShastaExtraData(100, big.NewInt(-1)) + require.ErrorContains(t, err, "proposal ID is negative") + + tooLarge := new(big.Int).Lsh(big.NewInt(1), uint(params.ShastaExtraDataProposalIDLength*8)) + _, err = EncodeShastaExtraData(100, tooLarge) + require.ErrorContains(t, err, "proposal ID too large") +}