diff --git a/op-chain-ops/script/deploy.go b/op-chain-ops/script/deploy.go index 9c39121a1db2b..b1d1a18b58de0 100644 --- a/op-chain-ops/script/deploy.go +++ b/op-chain-ops/script/deploy.go @@ -211,9 +211,15 @@ func (b *forgeScriptBackendImpl) Deploy(artifact *foundry.Artifact, label string b.host.AllowCheatcodes(address) // before constructor execution, give our script cheatcode access b.host.state.MakeExcluded(address) // scripts are persistent across forks - // disable contract size constraints + // disable contract size constraints for script deployment + wasNoMaxCodeSize := b.host.noMaxCodeSize b.host.EnforceMaxCodeSize(false) - defer b.host.EnforceMaxCodeSize(true) + defer func() { + // Only re-enable if it wasn't originally disabled + if !wasNoMaxCodeSize { + b.host.EnforceMaxCodeSize(true) + } + }() // deploy the script deployedAddr, err := b.host.Create(deployer, artifact.Bytecode.Object) diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index b94c05bc6d0f5..6f4e303591e9b 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -117,6 +117,9 @@ type Host struct { // useCreate2Deployer uses the Create2Deployer for broadcasted // create2 calls. useCreate2Deployer bool + + // noMaxCodeSize disables the maximum contract bytecode size check. + noMaxCodeSize bool } type HostOption func(h *Host) @@ -161,6 +164,15 @@ func WithCreate2Deployer() HostOption { } } +// WithNoMaxCodeSize disables the maximum contract bytecode size check. +// This is useful for development environments where contracts may be compiled +// without optimizations and exceed the standard 24KB limit. +func WithNoMaxCodeSize() HostOption { + return func(h *Host) { + h.noMaxCodeSize = true + } +} + // NewHost creates a Host that can load contracts from the given Artifacts FS, // and with an EVM initialized to the given executionContext. // Optionally src-map loading may be enabled, by providing a non-nil srcFS to read sources from. @@ -298,6 +310,11 @@ func NewHost( h.env = WrapEVM(vm.NewEVM(blockContext, h.state, h.chainCfg, vmCfg)) h.env.SetTxContext(txContext) + // Apply noMaxCodeSize after EVM is initialized + if h.noMaxCodeSize { + h.EnforceMaxCodeSize(false) + } + return h } diff --git a/op-chain-ops/script/script_test.go b/op-chain-ops/script/script_test.go index 814bac637f73f..8bac8afcf8bbe 100644 --- a/op-chain-ops/script/script_test.go +++ b/op-chain-ops/script/script_test.go @@ -472,3 +472,57 @@ func TestScriptErrorHandling(t *testing.T) { }) } } + +func TestWithNoMaxCodeSize(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + af := foundry.OpenArtifactsDir("./testdata/test-artifacts") + scriptContext := DefaultContext + deployer := scriptContext.Sender + + // Create init code that deploys a contract with >24KB runtime code + // Init code structure: + // PUSH2 0x6400 (25600 bytes = 25KB) + // PUSH1 0x0c (offset where runtime code starts) + // PUSH1 0x00 (memory destination) + // CODECOPY + // PUSH2 0x6400 (size to return) + // PUSH1 0x00 (memory offset) + // RETURN + runtimeSize := 25 * 1024 // 25KB runtime code + initCode := []byte{ + 0x61, 0x64, 0x00, // PUSH2 0x6400 + 0x60, 0x0c, // PUSH1 0x0c (12 bytes - length of this init code) + 0x60, 0x00, // PUSH1 0x00 + 0x39, // CODECOPY + 0x61, 0x64, 0x00, // PUSH2 0x6400 + 0x60, 0x00, // PUSH1 0x00 + 0xf3, // RETURN + } + // Append runtime code (can be any data, we'll use zeros) + runtimeCode := make([]byte, runtimeSize) + largeBytecode := append(initCode, runtimeCode...) + + t.Run("WithNoMaxCodeSize allows large contracts", func(t *testing.T) { + h := NewHost(logger, af, nil, scriptContext, WithNoMaxCodeSize()) + require.True(t, h.noMaxCodeSize, "noMaxCodeSize flag should be set") + require.True(t, h.env.Config().NoMaxCodeSize, "EVM should have NoMaxCodeSize enabled") + + addr, err := h.Create(deployer, largeBytecode) + require.NoError(t, err, "Should deploy large contract when NoMaxCodeSize is enabled") + require.NotEqual(t, common.Address{}, addr, "Should return valid address") + + // Verify the code was actually deployed + code := h.GetCode(addr) + require.NotEmpty(t, code, "Contract code should be deployed") + }) + + t.Run("Default behavior rejects large contracts", func(t *testing.T) { + h := NewHost(logger, af, nil, scriptContext) + require.False(t, h.noMaxCodeSize, "noMaxCodeSize flag should be false by default") + require.False(t, h.env.Config().NoMaxCodeSize, "EVM should enforce max code size by default") + + _, err := h.Create(deployer, largeBytecode) + require.Error(t, err, "Should reject large contract by default") + require.Contains(t, err.Error(), "max code size", "Error should mention max code size") + }) +} diff --git a/op-chain-ops/script/with.go b/op-chain-ops/script/with.go index 0823bf9b7fbd8..9bfc9dffd692c 100644 --- a/op-chain-ops/script/with.go +++ b/op-chain-ops/script/with.go @@ -44,9 +44,15 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun return nil, nil, fmt.Errorf("failed to make bindings: %w", err) } - // Scripts can be very large + // Scripts can be very large - disable contract size constraints for script deployment + wasNoMaxCodeSize := h.noMaxCodeSize h.EnforceMaxCodeSize(false) - defer h.EnforceMaxCodeSize(true) + defer func() { + // Only re-enable if it wasn't originally disabled + if !wasNoMaxCodeSize { + h.EnforceMaxCodeSize(true) + } + }() // deploy the script contract deployedAddr, err := h.Create(deployer, artifact.Bytecode.Object) if err != nil { diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index f0ce4b723bcc3..80a3cc1b5b729 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -316,6 +316,7 @@ func ApplyPipeline( opts.Logger, deployer, bundle.L1, + script.WithNoMaxCodeSize(), // Allow unoptimized contracts from the forge lite profile in genesis deployments ) if err != nil { return fmt.Errorf("failed to create L1 script host: %w", err)