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") +}