Skip to content
Draft
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
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
.github
**/*_test.go
**/testdata/
**/test/
docs/
bin/
cre
cre-admin
*.test
.env
.env.*
node_modules/
**/.vite/
c2w/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ cre-admin
dist
*.wasm
*.wasm.br
!web/wasm-simulate-demo/assets/manifest.json
!web/wasm-simulate-demo/assets/cre-environments.json
!web/wasm-simulate-demo/assets/headless-simulation.sample.json
!web/wasm-simulate-demo/assets/.gitkeep

# env files
*.env
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ require (
github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260323124644-faea187e6997
github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5
github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0
github.com/smartcontractkit/cre-sdk-go v1.11.0
github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v1.0.0-beta.12
github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/solana v0.1.0-beta.1
Expand Down Expand Up @@ -330,6 +330,7 @@ require (
github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260520103847-15ca4de9dba9 // indirect
github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a // indirect
github.com/smartcontractkit/cld-changesets v0.4.0 // indirect
github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron v1.0.0-beta.0 // indirect
github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect
github.com/smartcontractkit/libocr v0.0.0-20260508200755-99940c85383c // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/prometheus/prometheus v0.304.2 h1:HhjbaAwet87x8Be19PFI/5W96UMubGy3zt24kayEuh4=
github.com/prometheus/prometheus v0.311.3 h1:3IrVxQv6v5i/ZCGi6OrYeBhtCwaPTn6Z3DYruXoYm3M=
github.com/prometheus/prometheus v0.311.3/go.mod h1:gjsCxTKtHO1Q8T9333u1s+lUR1OjPyM7ruuGH8RvVyo=
github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4=
Expand Down Expand Up @@ -1234,6 +1235,8 @@ github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.202510141
github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014143056-a0c6328c91e9/go.mod h1:ea1LESxlSSOgc2zZBqf1RTkXTMthHaspdqUHd7W4lF0=
github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8 h1:xPDpOmxTlT2RW+pPUElGa0/y02V/MAHIPD8DEtEBLfE=
github.com/smartcontractkit/chainlink/deployment v0.0.0-20260521170940-67f9a4b233f8/go.mod h1:WL7W/YQO5pQ1Nexm4lvd/SztM2OzbhaIhJKyrfMU8QQ=
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0 h1:t30uKSMNtlj84nTH5qm2oNOGS6ff2RF8tSKlSaWCWgk=
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0/go.mod h1:qIwJWlVQ9MxUFHfgQePZteAov/C+LIQ1LAhJ1abPBO0=
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8 h1:naZxYMHsgi+JVChySLtaqAfgRM3Az2+HpvIzWNFcPRo=
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260521170940-67f9a4b233f8/go.mod h1:8Ppr/wQrc9GVwQow3Tw2JsNjNjqMhZTyU32cm5OFMzo=
github.com/smartcontractkit/cld-changesets v0.4.0 h1:S6yNRj6FssyyKbxLHTbC9X9U4qsph17xiiBBT6DGyNE=
Expand Down
5 changes: 5 additions & 0 deletions scripts/build-wasm-demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "${ROOT}"
go run ./web/wasm-simulate-demo/tools/build_assets
67 changes: 67 additions & 0 deletions test/e2e_hello_world_wasm_build_simulate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package test

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/cre-cli/internal/constants"
"github.com/smartcontractkit/cre-cli/internal/credentials"
"github.com/smartcontractkit/cre-cli/internal/settings"
)

func TestE2E_WorkflowBuildThenSimulateWithWasm_HelloWorldGo(t *testing.T) {
tempDir := t.TempDir()
projectName := "e2e-build-sim-wasm"
workflowName := "helloWorkflow"
projectRoot := filepath.Join(tempDir, projectName)
workflowDir := filepath.Join(projectRoot, workflowName)
// Simulate runs with cwd set to the workflow directory; --wasm is relative to that dir.
wasmPath := "binary.wasm"

t.Setenv(settings.EthPrivateKeyEnvVar, "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
t.Setenv(credentials.CreApiKeyVar, "test-api")

gqlSrv := NewGraphQLMockServerGetOrganization(t)
defer gqlSrv.Close()

scaffoldHelloWorldGoProject(t, projectRoot, workflowName)

require.FileExists(t, filepath.Join(projectRoot, constants.DefaultProjectSettingsFileName))
require.DirExists(t, workflowDir)
require.FileExists(t, filepath.Join(workflowDir, "main.go"))
require.FileExists(t, filepath.Join(workflowDir, "workflow.go"))

simulateArgs := []string{
"workflow", "simulate",
workflowName,
"--project-root", projectRoot,
"--non-interactive",
"--trigger-index=0",
"--target=staging-settings",
}

baselineOut := runCLI(t, projectRoot, simulateArgs...)
assertHelloWorldSimulationResult(t, baselineOut)
require.Contains(t, stripANSI(baselineOut), "Workflow compiled",
"baseline simulate should compile inline")

buildOut := runCLI(t, projectRoot, "workflow", "build", workflowName)
require.FileExists(t, filepath.Join(workflowDir, "binary.wasm"))
info, err := os.Stat(filepath.Join(workflowDir, "binary.wasm"))
require.NoError(t, err)
require.Positive(t, info.Size())
require.Contains(t, stripANSI(buildOut), "Build output written")

wasmSimulateArgs := append([]string(nil), simulateArgs...)
wasmSimulateArgs = append(wasmSimulateArgs, "--wasm", wasmPath)
wasmOut := runCLI(t, projectRoot, wasmSimulateArgs...)

cleanWasmOut := stripANSI(wasmOut)
require.Contains(t, cleanWasmOut, "Loaded WASM binary")
require.NotContains(t, cleanWasmOut, "Workflow compiled",
"--wasm should skip inline compilation")
assertHelloWorldSimulationResult(t, wasmOut)
}
90 changes: 90 additions & 0 deletions test/hello_world_scaffold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package test

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/cre-cli/internal/constants"
"github.com/smartcontractkit/cre-cli/internal/testutil"
"github.com/smartcontractkit/cre-cli/internal/templaterepo"
)

// scaffoldHelloWorldGoProject lays out the built-in hello-world-go template with
// project/workflow settings and Go module deps (without cre init).
func scaffoldHelloWorldGoProject(t *testing.T, projectRoot, workflowName string) {
t.Helper()

logger := testutil.NewTestLogger()
require.NoError(t, os.MkdirAll(projectRoot, 0755))
require.NoError(t, templaterepo.ScaffoldBuiltIn(logger, "hello-world-go", projectRoot, workflowName))

projectYAML := `staging-settings:
rpcs:
- chain-name: ethereum-testnet-sepolia
url: https://sepolia.infura.io/v3
production-settings:
rpcs:
- chain-name: ethereum-testnet-sepolia
url: https://sepolia.infura.io/v3
`
workflowYAML := fmt.Sprintf(`staging-settings:
user-workflow:
workflow-name: "%s-staging"
workflow-artifacts:
workflow-path: "./main.go"
config-path: "./config.staging.json"
secrets-path: "../secrets.yaml"
production-settings:
user-workflow:
workflow-name: "%s-production"
workflow-artifacts:
workflow-path: "./main.go"
config-path: "./config.production.json"
secrets-path: "../secrets.yaml"
`, workflowName, workflowName)

require.NoError(t, os.WriteFile(
filepath.Join(projectRoot, constants.DefaultProjectSettingsFileName),
[]byte(projectYAML),
0600,
))
require.NoError(t, os.WriteFile(
filepath.Join(projectRoot, workflowName, constants.DefaultWorkflowSettingsFileName),
[]byte(workflowYAML),
0600,
))

initializeHelloWorldGoModule(t, projectRoot)
}

func initializeHelloWorldGoModule(t *testing.T, projectRoot string) {
t.Helper()

moduleName := filepath.Base(projectRoot)
cmd := exec.Command("go", "mod", "init", moduleName)
cmd.Dir = projectRoot
require.NoError(t, cmd.Run(), "go mod init failed")

// cron@v1.3.0 does not resolve on the public module proxy (pulls cre-sdk-go@v1.3.0).
// v1.0.0-beta.0 matches test/test_project/blank_workflow and works with SdkVersion.
deps := []string{
"github.com/smartcontractkit/cre-sdk-go@" + constants.SdkVersion,
"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron@v1.0.0-beta.0",
}
for _, dep := range deps {
getCmd := exec.Command("go", "get", dep)
getCmd.Dir = projectRoot
out, err := getCmd.CombinedOutput()
require.NoError(t, err, "go get %s failed:\n%s", dep, out)
}

tidyCmd := exec.Command("go", "mod", "tidy")
tidyCmd.Dir = projectRoot
out, err := tidyCmd.CombinedOutput()
require.NoError(t, err, "go mod tidy failed:\n%s", out)
}
84 changes: 84 additions & 0 deletions test/simulate_output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package test

import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"regexp"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

const simulationResultMarker = "Workflow Simulation Result"

var ansiRE = regexp.MustCompile(`\x1b\[[0-9;]*m`)

// stripANSI removes ANSI escape codes from CLI output.
func stripANSI(s string) string {
return ansiRE.ReplaceAllString(s, "")
}

type helloWorldExecutionResult struct {
Result string `json:"Result"`
}

// extractSimulationResultJSON returns the JSON object printed after the simulation result marker.
func extractSimulationResultJSON(out string) (string, error) {
idx := strings.Index(out, simulationResultMarker)
if idx < 0 {
return "", fmt.Errorf("%q not found in output", simulationResultMarker)
}

rest := out[idx+len(simulationResultMarker):]
start := strings.Index(rest, "{")
if start < 0 {
return "", fmt.Errorf("no JSON object after %q", simulationResultMarker)
}

dec := json.NewDecoder(strings.NewReader(rest[start:]))
var raw json.RawMessage
if err := dec.Decode(&raw); err != nil {
return "", fmt.Errorf("decode simulation result JSON: %w", err)
}
return string(raw), nil
}

func parseHelloWorldSimulationResult(t *testing.T, out string) helloWorldExecutionResult {
t.Helper()
clean := stripANSI(out)
jsonStr, err := extractSimulationResultJSON(clean)
require.NoError(t, err, "output:\n%s", clean)

var result helloWorldExecutionResult
require.NoError(t, json.Unmarshal([]byte(jsonStr), &result), "json: %s", jsonStr)
return result
}

func assertHelloWorldSimulationResult(t *testing.T, out string) helloWorldExecutionResult {
t.Helper()
clean := stripANSI(out)
require.Contains(t, clean, simulationResultMarker, "output:\n%s", clean)

result := parseHelloWorldSimulationResult(t, out)
require.Contains(t, result.Result, "Fired at", "Result field should contain cron timestamp prefix")
return result
}

// runCLI runs the cre CLI from dir and returns combined stdout+stderr (for output parsing).
func runCLI(t *testing.T, dir string, args ...string) string {
t.Helper()
var stdout, stderr bytes.Buffer
cmd := exec.Command(CLIPath, args...)
cmd.Dir = dir
cmd.Stdout = &stdout
cmd.Stderr = &stderr
require.NoError(t, cmd.Run(),
"cre %s failed:\nSTDOUT:\n%s\nSTDERR:\n%s",
strings.Join(args, " "),
stdout.String(),
stderr.String())
return stdout.String() + stderr.String()
}
48 changes: 48 additions & 0 deletions web/wasm-simulate-demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Build hello-world workflow WASM, then serve the static demo with assets baked in.
FROM golang:1.26-bookworm AS wasm-builder

WORKDIR /src

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN go run ./web/wasm-simulate-demo/tools/build_assets

# Verify assets were produced (fail the image build if WASM is missing).
RUN test -f /src/web/wasm-simulate-demo/assets/hello-world.wasm \
&& test -f /src/web/wasm-simulate-demo/assets/manifest.json

FROM nginx:1.27-alpine AS runtime

COPY web/wasm-simulate-demo/nginx.conf /etc/nginx/conf.d/default.conf

# Static UI (vendor, JS, CSS) from context.
COPY web/wasm-simulate-demo/index.html \
web/wasm-simulate-demo/wallet.html \
web/wasm-simulate-demo/app.js \
web/wasm-simulate-demo/wallet-app.js \
web/wasm-simulate-demo/cre-host.js \
web/wasm-simulate-demo/wasi-stubs.js \
web/wasm-simulate-demo/payload.js \
web/wasm-simulate-demo/styles.css \
/usr/share/nginx/html/

COPY web/wasm-simulate-demo/lib/ /usr/share/nginx/html/lib/

COPY web/wasm-simulate-demo/assets/cre-environments.json \
/usr/share/nginx/html/assets/

COPY web/wasm-simulate-demo/vendor/ /usr/share/nginx/html/vendor/

COPY web/wasm-simulate-demo/assets/headless-simulation.sample.json \
/usr/share/nginx/html/assets/

# Prebuilt WASM + manifest from the Go builder stage.
COPY --from=wasm-builder /src/web/wasm-simulate-demo/assets/ /usr/share/nginx/html/assets/

EXPOSE 80

HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1/assets/manifest.json || exit 1
Loading
Loading