Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,6 @@ Inside Docker, native project browsing is unavailable because the backend runs i
"contractName": "MyStateOverride",
"source": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\ncontract MyStateOverride { fallback() external {} }"
},
"compiler": {
"viaIR": true,
"optimize": true,
"optimizerRuns": 200,
"revertStrings": "default"
},
"decodeInternal": false,
"sender": "0x0000000000000000000000000000000000000000",
"target": "0x0000000000000000000000000000000000000000",
Expand All @@ -184,7 +178,7 @@ to the final `forge-kyber test --json` command.

`projectPath` is optional. When provided, the backend treats it as another Foundry project, runs `forge-kyber build src --root <projectPath>`, copies `contracts/test/SimulateTxRunner.t.sol` into a deterministic content-hash file under `<projectPath>/test/`, runs `forge-kyber test --json` against that copied test with `--root <projectPath>`, then removes the temporary test file after the last active run using it finishes. Relative paths are resolved against the backend repo root. Paths beginning with `~` are expanded to the backend process user's home directory before validation.

`compiler` is optional and maps to popular Forge compiler flags. Supported fields are `use`, `offline`, `noAutoDetect`, `viaIR`, `useLiteralContent`, `noMetadata`, `evmVersion`, `optimize`, `optimizerRuns`, and `revertStrings`. The backend only passes `use` and `evmVersion` when they are explicitly provided. The state override `forge-kyber inspect` compile and final `forge-kyber test` run default `viaIR` and `optimize` to `true`; external-project `forge-kyber build src` uses the target project's defaults unless compiler fields are explicitly set.
Compiler settings come from the selected Foundry project. The backend does not accept compiler options on the simulation request or add request-level compiler flags to `forge-kyber build`, `forge-kyber inspect`, or `forge-kyber test`.

## Tx Request

Expand Down
18 changes: 0 additions & 18 deletions backend/internal/httpapi/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ func registerOpenAPISchemas(schemas openapi3.Schemas) error {
{"ERC20ApprovalOverride", model.ERC20ApprovalOverride{}},
{"ERC721ApprovalOverride", model.ERC721ApprovalOverride{}},
{"StateOverride", model.StateOverride{}},
{"CompilerConfig", model.CompilerConfig{}},
{"SimulateResponse", model.SimulateResponse{}},
{"ERC20Transfer", model.ERC20Transfer{}},
{"BalanceAnalysis", model.BalanceAnalysis{}},
Expand Down Expand Up @@ -264,23 +263,6 @@ func enrichOpenAPISchemas(schemas openapi3.Schemas) {
setPropertyExample(schemas, "ERC721ApprovalOverride", "spender", "0x0000000000000000000000000000000000000002")
setPropertyExample(schemas, "StateOverride", "contractName", "MyStateOverride")

setPropertyDescription(schemas, "CompilerConfig", "use", "Maps to forge-kyber --use <SOLC_VERSION>. Omitted unless explicitly provided.")
setPropertyDescription(schemas, "CompilerConfig", "offline", "Maps to --offline.")
setPropertyDescription(schemas, "CompilerConfig", "noAutoDetect", "Maps to --no-auto-detect.")
setPropertyDescription(schemas, "CompilerConfig", "viaIR", "Maps to --via-ir. Defaults to true for this backend.")
setPropertyDefault(schemas, "CompilerConfig", "viaIR", true)
setPropertyDescription(schemas, "CompilerConfig", "useLiteralContent", "Maps to --use-literal-content.")
setPropertyDescription(schemas, "CompilerConfig", "noMetadata", "Maps to --no-metadata.")
setPropertyDescription(schemas, "CompilerConfig", "evmVersion", "Maps to --evm-version <VERSION>. Omitted unless explicitly provided.")
setPropertyDescription(schemas, "CompilerConfig", "optimize", "Maps to --optimize. Defaults to true for this backend.")
setPropertyDefault(schemas, "CompilerConfig", "optimize", true)
setPropertyDescription(schemas, "CompilerConfig", "optimizerRuns", "Maps to --optimizer-runs <RUNS>.")
setPropertyMinMax(schemas, "CompilerConfig", "optimizerRuns", 0, 4294967295)
setPropertyExample(schemas, "CompilerConfig", "optimizerRuns", 200)
setPropertyDescription(schemas, "CompilerConfig", "revertStrings", "Maps to --revert-strings <REVERT>.")
setPropertyEnum(schemas, "CompilerConfig", "revertStrings", "default", "strip", "debug", "verboseDebug")
setPropertyExample(schemas, "CompilerConfig", "revertStrings", "default")

if schema := schemaValue(schemas, "Uint256"); schema != nil {
schema.Type = &openapi3.Types{"string"}
schema.Pattern = uint256Pattern
Expand Down
7 changes: 5 additions & 2 deletions backend/internal/httpapi/openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func TestOpenAPIEndpoint(t *testing.T) {
if !ok {
t.Fatalf("missing schemas in spec: %#v", components)
}
if _, ok := schemas["CompilerConfig"]; !ok {
t.Fatalf("missing CompilerConfig schema: %#v", schemas)
if _, ok := schemas["CompilerConfig"]; ok {
t.Fatalf("CompilerConfig should not be exposed: %#v", schemas)
}
if _, ok := schemas["SimulationRecord"]; !ok {
t.Fatalf("missing SimulationRecord schema: %#v", schemas)
Expand All @@ -81,6 +81,9 @@ func TestOpenAPIEndpoint(t *testing.T) {
if _, ok := properties["projectSourceFiles"]; ok {
t.Fatalf("projectSourceFiles should be managed by /projects/default/source, not /simulation: %#v", properties)
}
if _, ok := properties["compiler"]; ok {
t.Fatalf("compiler options should follow project settings, not /simulation: %#v", properties)
}
if _, ok := properties["decodeInternal"]; !ok {
t.Fatalf("decodeInternal should be a request property: %#v", properties)
}
Expand Down
14 changes: 0 additions & 14 deletions backend/internal/model/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ type SimulateRequest struct {
ERC721ApprovalOverrides []ERC721ApprovalOverride `json:"erc721ApprovalOverrides,omitempty" validate:"dive"`
StateOverride *StateOverride `json:"stateOverride,omitempty"`
StateOverrideBytecode string `json:"stateOverrideBytecode,omitempty" validate:"hex_bytes"`
Compiler *CompilerConfig `json:"compiler,omitempty"`
DecodeInternal bool `json:"decodeInternal"`
Sender string `json:"sender" validate:"required,eth_address"`
Target string `json:"target" validate:"required,eth_address"`
Expand Down Expand Up @@ -79,19 +78,6 @@ type StateOverride struct {
ContractName string `json:"contractName,omitempty"`
}

type CompilerConfig struct {
Use string `json:"use,omitempty"`
Offline bool `json:"offline,omitempty"`
NoAutoDetect bool `json:"noAutoDetect,omitempty"`
ViaIR *bool `json:"viaIR,omitempty"`
UseLiteralContent bool `json:"useLiteralContent,omitempty"`
NoMetadata bool `json:"noMetadata,omitempty"`
EVMVersion string `json:"evmVersion,omitempty"`
Optimize *bool `json:"optimize,omitempty"`
OptimizerRuns *uint32 `json:"optimizerRuns,omitempty"`
RevertStrings string `json:"revertStrings,omitempty"`
}

type HealthResponse struct {
OK bool `json:"ok"`
Chains int `json:"chains"`
Expand Down
33 changes: 4 additions & 29 deletions backend/internal/simulation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (s *Service) Simulate(parent context.Context, req model.SimulateRequest) (m

if execution.BuildSrc {
slog.Info("forge build src started", "run_id", runID, "root", execution.Root)
buildResult := s.buildProjectSrc(ctx, execution, req.Compiler)
buildResult := s.buildProjectSrc(ctx, execution)
logForgeResult(runID, "build project src", buildResult)
if buildResult.Err != nil {
status := populateForgeFailure(&resp, start, buildResult, rpcURL, req.Chain, "build project src", buildResult.Err)
Expand All @@ -284,7 +284,7 @@ func (s *Service) Simulate(parent context.Context, req model.SimulateRequest) (m
slog.Info("state override source written", "run_id", runID, "contract", contractName, "path", statePath)

slog.Info("state override compile started", "run_id", runID, "root", execution.Root, "contract", contractName)
bytecode, compileResult, err := s.compileStateOverride(ctx, execution.Root, statePath, contractName, req.Compiler)
bytecode, compileResult, err := s.compileStateOverride(ctx, execution.Root, statePath, contractName)
logForgeResult(runID, "compile state override", compileResult)
if err != nil {
status := populateForgeFailure(&resp, start, compileResult, rpcURL, req.Chain, "compile state override", err)
Expand Down Expand Up @@ -322,8 +322,6 @@ func (s *Service) Simulate(parent context.Context, req model.SimulateRequest) (m
if req.DecodeInternal {
forgeArgs = append(forgeArgs, "--decode-internal")
}
compilerArgs := solidity.ForgeCompilerArgs(req.Compiler)
forgeArgs = append(forgeArgs, compilerArgs...)
if etherscanAPIKey != "" {
forgeArgs = append(forgeArgs, "--etherscan-api-key", etherscanAPIKey)
}
Expand All @@ -336,7 +334,6 @@ func (s *Service) Simulate(parent context.Context, req model.SimulateRequest) (m
"match_test", simulationTestName,
"anvil_rpc", anvilRPCURL,
"decode_internal", req.DecodeInternal,
"compiler_args", len(compilerArgs),
)
result := s.forge.RunWithEnv(ctx, []string{inputPathEnvName + "=" + inputPath}, forgeArgs...)
logForgeResult(runID, "forge test", result)
Expand Down Expand Up @@ -530,10 +527,6 @@ func (s *Service) validateRequest(ctx context.Context, req *model.SimulateReques
req.Data = normalizedData
ensureSenderLabel(req)

if err := validateCompilerConfig(req.Compiler); err != nil {
return "", err
}

return rpcURL, nil
}

Expand Down Expand Up @@ -621,22 +614,6 @@ func pathSuffixes(value string) []string {
return suffixes
}

func validateCompilerConfig(config *model.CompilerConfig) error {
if config == nil {
return nil
}

config.Use = strings.TrimSpace(config.Use)
config.EVMVersion = strings.TrimSpace(config.EVMVersion)
config.RevertStrings = strings.TrimSpace(config.RevertStrings)
switch config.RevertStrings {
case "", "default", "strip", "debug", "verboseDebug":
default:
return fmt.Errorf("compiler.revertStrings must be one of default, strip, debug, or verboseDebug")
}
return nil
}

func (s *Service) prepareFoundryExecution(req *model.SimulateRequest, runID string) (foundryExecution, error) {
localRoot := filepath.Join(s.cfg.RepoRoot, localFoundryDir)
localTestPath := filepath.Join(localRoot, filepath.FromSlash(localSimulationRelPath))
Expand Down Expand Up @@ -822,9 +799,8 @@ func (s *Service) releaseTestCopy(testPath string) {
}
}

func (s *Service) buildProjectSrc(ctx context.Context, execution foundryExecution, compiler *model.CompilerConfig) forge.Result {
func (s *Service) buildProjectSrc(ctx context.Context, execution foundryExecution) forge.Result {
args := []string{"build", "src", "--root", execution.Root, "--color", "never"}
args = append(args, solidity.ForgeCompilerArgsExplicit(compiler)...)
return s.forge.Run(ctx, args...)
}

Expand Down Expand Up @@ -888,14 +864,13 @@ func safeRunID(runID string) string {
return strings.NewReplacer(".", "_", "-", "_").Replace(runID)
}

func (s *Service) compileStateOverride(ctx context.Context, projectRoot string, sourcePath string, contractName string, compiler *model.CompilerConfig) (string, forge.Result, error) {
func (s *Service) compileStateOverride(ctx context.Context, projectRoot string, sourcePath string, contractName string) (string, forge.Result, error) {
contractID, err := solidity.ContractIdentifier(projectRoot, sourcePath, contractName)
if err != nil {
return "", forge.Result{}, err
}

args := []string{"inspect", contractID, "bytecode", "--root", projectRoot, "--contracts", ".", "--color", "never"}
args = append(args, solidity.ForgeCompilerArgs(compiler)...)
result := s.forge.Run(ctx, args...)
if result.Err != nil {
return "", result, result.Err
Expand Down
46 changes: 29 additions & 17 deletions backend/internal/simulation/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,6 @@ func TestSimulateWETHBalanceApprovalAndTransferFrom(t *testing.T) {
Amount: model.Uint256(amount),
},
},
Compiler: &model.CompilerConfig{
ViaIR: boolPtr(true),
Optimize: boolPtr(true),
OptimizerRuns: uint32Ptr(200),
EVMVersion: "cancun",
RevertStrings: "default",
},
DecodeInternal: true,
Sender: spender,
Target: wethAddress,
Expand Down Expand Up @@ -443,8 +436,8 @@ func TestSimulateExternalProjectBuildsSrcCompilesOverrideAndRunsCopiedTest(t *te
if !hasArgSequence(buildArgs, "build", "src") || !hasArgSequence(buildArgs, "--root", projectRoot) {
t.Fatalf("unexpected build args: %#v", buildArgs)
}
if containsArg(buildArgs, "--via-ir") {
t.Fatalf("build should use target project defaults unless request compiler fields are set: %#v", buildArgs)
if hasCompilerArg(buildArgs) {
t.Fatalf("build should use target project compiler settings: %#v", buildArgs)
}

inspectArgs := fake.calls[1]
Expand All @@ -454,6 +447,9 @@ func TestSimulateExternalProjectBuildsSrcCompilesOverrideAndRunsCopiedTest(t *te
!hasArgSequence(inspectArgs, "--root", projectRoot) {
t.Fatalf("unexpected inspect args: %#v", inspectArgs)
}
if hasCompilerArg(inspectArgs) {
t.Fatalf("inspect should use target project compiler settings: %#v", inspectArgs)
}

testArgs := fake.calls[2]
testHash := sha256.Sum256(testSource)
Expand All @@ -471,6 +467,9 @@ func TestSimulateExternalProjectBuildsSrcCompilesOverrideAndRunsCopiedTest(t *te
containsArg(testArgs, "--non-interactive") {
t.Fatalf("unexpected test args: %#v", testArgs)
}
if hasCompilerArg(testArgs) {
t.Fatalf("test should use target project compiler settings: %#v", testArgs)
}
if _, ok := envValue(fake.envs[2], inputPathEnvName); !ok {
t.Fatalf("forge test env missing input path: %#v", fake.envs)
}
Expand Down Expand Up @@ -1114,14 +1113,6 @@ func leftPadHex(value string, length int) string {
return strings.Repeat("0", length-len(value)) + value
}

func boolPtr(value bool) *bool {
return &value
}

func uint32Ptr(value uint32) *uint32 {
return &value
}

func forgeJSONTrace() string {
return forgeJSONTraceWithCall(true, "transfer")
}
Expand Down Expand Up @@ -1389,6 +1380,27 @@ func containsArg(args []string, want string) bool {
return false
}

func hasCompilerArg(args []string) bool {
compilerArgs := map[string]struct{}{
"--evm-version": {},
"--no-auto-detect": {},
"--no-metadata": {},
"--offline": {},
"--optimizer-runs": {},
"--revert-strings": {},
"--use": {},
"--use-literal-content": {},
"--via-ir": {},
}
for _, arg := range args {
_, ok := compilerArgs[arg]
if ok || strings.HasPrefix(arg, "--optimize") {
return true
}
}
return false
}

func hasEnvPrefix(env []string, prefix string) bool {
_, ok := envValueByPrefix(env, prefix)
return ok
Expand Down
72 changes: 0 additions & 72 deletions backend/internal/solidity/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"path/filepath"
"regexp"
"strings"

"foundry-tx-simulator/backend/internal/model"
)

var (
Expand All @@ -17,76 +15,6 @@ var (
hexValuePattern = regexp.MustCompile(`0x[0-9a-fA-F]+`)
)

func ForgeCompilerArgs(config *model.CompilerConfig) []string {
return forgeCompilerArgs(config, true)
}

func ForgeCompilerArgsExplicit(config *model.CompilerConfig) []string {
return forgeCompilerArgs(config, false)
}

func forgeCompilerArgs(config *model.CompilerConfig, useDefaults bool) []string {
if useDefaults {
config = effectiveCompilerConfig(config)
}
if config == nil {
return nil
}

args := make([]string, 0, 16)
if config.NoAutoDetect {
args = append(args, "--no-auto-detect")
}
if strings.TrimSpace(config.Use) != "" {
args = append(args, "--use", strings.TrimSpace(config.Use))
}
if config.Offline {
args = append(args, "--offline")
}
if config.ViaIR != nil && *config.ViaIR {
args = append(args, "--via-ir")
}
if config.UseLiteralContent {
args = append(args, "--use-literal-content")
}
if config.NoMetadata {
args = append(args, "--no-metadata")
}
if strings.TrimSpace(config.EVMVersion) != "" {
args = append(args, "--evm-version", strings.TrimSpace(config.EVMVersion))
}
if config.Optimize != nil {
args = append(args, "--optimize="+fmt.Sprintf("%t", *config.Optimize))
}
if config.OptimizerRuns != nil {
args = append(args, "--optimizer-runs", fmt.Sprintf("%d", *config.OptimizerRuns))
}
if strings.TrimSpace(config.RevertStrings) != "" {
args = append(args, "--revert-strings", strings.TrimSpace(config.RevertStrings))
}
return args
}

func effectiveCompilerConfig(config *model.CompilerConfig) *model.CompilerConfig {
viaIR := true
optimize := true
if config == nil {
return &model.CompilerConfig{
ViaIR: &viaIR,
Optimize: &optimize,
}
}

effective := *config
if effective.ViaIR == nil {
effective.ViaIR = &viaIR
}
if effective.Optimize == nil {
effective.Optimize = &optimize
}
return &effective
}

func ValidateAddress(field string, value string) error {
if !addressPattern.MatchString(value) {
return fmt.Errorf("%s must be a 20-byte hex address", field)
Expand Down
Loading
Loading