Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6cdebc3
Added system test for happy path of erc20 flow
alsala Apr 15, 2026
e15d6b2
Added more unit tests and applied some code refactoring to reduce dup…
alsala Apr 15, 2026
0de1ea1
Modifications after code review
alsala Apr 15, 2026
a218c28
WIP: full system test with real simulated chain
alsala Apr 16, 2026
dae8adf
Aded subgraph checks in full stack test
alsala Apr 16, 2026
c53da3f
Modifications useful for adding a wallet driver usable in full stack …
alsala Apr 20, 2026
a4c8aaa
Extract shared WASM test helpers into runtime/wasm-go/testhelpers and…
alsala Apr 22, 2026
4754a73
Aded erc20 main test
alsala Apr 22, 2026
cf9e1ca
Added more tests full stack path
alsala Apr 23, 2026
9d6b502
In getpublicbalance replaced live-RPC test with simulated-backend
alsala Apr 23, 2026
2ef2296
add TestMultiAppIsolation covering per-appID isolation
alsala Apr 23, 2026
82114c4
Added ERC20 balanceOf and test in wallet cmd
alsala Apr 23, 2026
c0f9a25
Added a test for authorizing a new deployer
alsala Apr 23, 2026
0f61f67
add TestDeployerRoleEnforcement verifying DEPLOYER_ROLE contract-sid…
alsala Apr 23, 2026
973f7b2
Merge branch 'dev' into as/full_system_test
alsala Apr 24, 2026
299cea0
Updated code comments with most recent changes
alsala Apr 24, 2026
e9cfcba
added full stack sys test covering coupled-restart keyset-recovery ha…
alsala Apr 24, 2026
97b2a9c
add fullstack sys test proving ProcessorEndpoint.InvalidSignature fir…
alsala Apr 24, 2026
d55d2f3
Added AssertNoStateUpdateErrors safety-net assertion to all happy-pat…
alsala Apr 24, 2026
34e9cf0
More fullstack system tests
alsala Apr 24, 2026
ba31b94
More fullstack system tests added
alsala Apr 24, 2026
5187990
Using vela-common-go definition for native-token constant
alsala Apr 28, 2026
18dc76c
Modifications after code review
alsala Apr 28, 2026
67f0eac
Modifications after code review. Referencing tag v0.1.1
alsala May 4, 2026
edbf4e3
Merge pull request #51 from HorizenOfficial/as/full_system_test
alsala May 4, 2026
1c27092
Fix in signature for downloading report
saratnt Jun 29, 2026
0824bee
Merge pull request #52 from HorizenOfficial/st/HZN-2913
paolocappelletti Jun 29, 2026
95f4e1b
Changes for v0.2.0
saratnt Jul 2, 2026
1d9fa09
Merge pull request #54 from HorizenOfficial/st/v0.2.0
saratnt Jul 2, 2026
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
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ Deploy note: the sender used by `novaw deployapp` must have `DEPLOYER_ROLE` on t
## CI / release

`.github/workflows/ci.yml` runs the wasm-go suite twice (fast + full), then the wallet suite, using Go 1.24 and TinyGo 0.39.0. Tags matching `v*` trigger a release that ships `production_build/payment_app.wasm`, `wallet/releases/novaw-linux`, and `wallet.conf.template` as GitHub Release artifacts. Bumping the Vela dependency version typically requires tagging both `vela` and `vela-common-go` at matching versions and updating both `go.mod` files together.

## Dependencies & NOTICES

Whenever dependencies change (any edit to either `go.mod` — version bumps, added, or removed modules), update the `NOTICES` file in the same change so its listed versions, licenses, and source links stay in sync with the `go.mod` files. `NOTICES` tracks the go-ethereum LGPL-3.0 relinking obligation and all third-party notices, so drift here is a licensing-compliance issue, not just documentation.
20 changes: 10 additions & 10 deletions NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Source code and build configuration (go.mod, go.sum) are provided to enable
this.

go-ethereum
Version: v1.16.1
Version: v1.17.2
License: GNU Lesser General Public License v3.0 (LGPL-3.0) (library code)
GNU General Public License v3.0 (GPL-3.0) (cmd/ binaries only)
Source: https://github.com/ethereum/go-ethereum
Expand All @@ -40,13 +40,13 @@ PERMISSIVE DEPENDENCIES - Go (Direct)
================================================================================

vela-common-go
Version: v0.0.25
Version: v0.2.0
License: BSL-1.1
Source: https://github.com/HorizenOfficial/vela-common-go
Notice: Copyright (c) 2025 The Horizen Foundation.

vela
Version: v0.0.25
Version: v0.2.0
License: BSL-1.1
Source: https://github.com/HorizenOfficial/vela
Notice: Copyright (c) 2025 The Horizen Foundation.
Expand Down Expand Up @@ -86,25 +86,25 @@ orderedmap
Notice: Copyright (c) 2020 Elliot Chance.

gnark-crypto
Version: v0.18.0
Version: v0.18.1
License: Apache-2.0
Source: https://github.com/consensys/gnark-crypto
Notice: Copyright (c) 2020 ConsenSys Software Inc.

go-eth-kzg
Version: v1.3.0
Version: v1.5.0
License: Apache-2.0
Source: https://github.com/crate-crypto/go-eth-kzg
Notice: Copyright (c) 2023 Crate Crypto.

c-kzg-4844
Version: v2.1.0
Version: v2.1.6
License: Apache-2.0
Source: https://github.com/ethereum/c-kzg-4844
Notice: Copyright (c) 2023 The Ethereum Foundation.

blst
Version: v0.3.14
Version: v0.3.16
License: Apache-2.0
Source: https://github.com/supranational/blst
Notice: Copyright (c) 2022 Supranational LLC.
Expand All @@ -128,19 +128,19 @@ zerolog
Notice: Copyright (c) 2017 Olivier Poitrey.

golang/snappy
Version: v0.0.5-0.20220116011046-fa5810519dcb
Version: v1.0.0
License: BSD-3-Clause
Source: https://github.com/golang/snappy
Notice: Copyright 2011 The Snappy-Go Authors.

golang.org/x/crypto
Version: v0.39.0
Version: v0.44.0
License: BSD-3-Clause
Source: https://github.com/golang/crypto
Notice: Copyright 2009 The Go Authors.

google.golang.org/protobuf
Version: v1.34.2
Version: v1.36.11
License: BSD-3-Clause
Source: https://github.com/protocolbuffers/protobuf-go
Notice: Copyright 2018 The Go Authors.
Expand Down
136 changes: 136 additions & 0 deletions runtime/wasm-go/fullstack_tests/deanonymize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main_test

import (
"encoding/json"
"fmt"
"math/big"
"os"
"path/filepath"
"strings"
"testing"

"github.com/HorizenOfficial/vela-nova/payment-app/testhelpers"
walletTestutil "github.com/HorizenOfficial/vela-nova/wallet/testutil"
"github.com/HorizenOfficial/vela/pkg/testutil/fullstack"
"github.com/stretchr/testify/require"
)

// TestDeanonymize_RealRoundTrip exercises the full authority deanonymization
// pipeline against real components — no synthetic injection. It is the
// end-to-end counterpart to TestAuthorityServiceGetReportRoundTrip (in the
// vela fullstack package), which only verifies the HTTP service boot and
// signature check against a hand-written report file.
//
// Flow:
// 1. Two wallet drivers: user (deposits ETH so there's something to report)
// and auth (acts as the authority — registered user + on-chain authority).
// 2. Both RegisterUser so the executor has both P521 pubkeys in its
// keyStore (required for encrypting the report to auth).
// 3. suite.RegisterAuthority grants auth's address DEFAULT_AUTHORITY role
// for the app — otherwise the on-chain Deanonymize submit reverts.
// 4. auth.RequestReport submits a Deanonymize request. The manager picks
// it up, the WASM guest returns report bytes, the executor encrypts
// those bytes to auth's P521 pubkey, and the manager writes the resulting
// DeanonymizationReport JSON to the reports dir.
// 5. auth.DownloadReport runs the /nonce + /getreport + decrypt flow
// against the in-process authority service, writing the decrypted
// report to a destination file.
// 6. The test reads that file and asserts the decrypted content carries
// both the expected ApplicationID/RequestID AND a non-empty reportData
// payload that references the user's address. This is what catches
// regressions in any layer — subgraph indexing of the RequestCompleted
// event, real executor-side encryption-to-requester, or HTTP boundary
// signature verification.
func TestDeanonymize_RealRoundTrip(t *testing.T) {
if os.Getenv("CI_FLAG") != "" {
t.Skip("Skipping fullstack test in CI environment")
}

t.Setenv("MANAGER_ARTIFACTS_PATH", t.TempDir())

suite := fullstack.NewFullStackSystemTestSuite(t, "wasmtime-payment",
testhelpers.NewNetworkLogConfig(), testhelpers.NewNetworkLogConfig())
defer suite.Cleanup()

require.NoError(t, suite.StartExecutor())
require.NoError(t, suite.StartManager())

driverUser := walletTestutil.NewWalletDriver(t, suite)
driverAuth := walletTestutil.NewWalletDriver(t, suite)

wasmBytes := testhelpers.BuildAndLoadWasmModule(t)
wasmPath := filepath.Join(t.TempDir(), "payment_app.wasm")
require.NoError(t, os.WriteFile(wasmPath, wasmBytes, 0o644))

appID, err := driverUser.DeployApp(t.Context(), wasmPath, "100 wei", nil)
require.NoError(t, err)
require.NotZero(t, appID)
driverAuth.SetApplicationID(appID)

// Both wallets register so the executor keyStore has their P521 pubkeys.
// Without auth's P521 entry, the executor's
// encryptDeanonymizationReport call would fail with
// CodePubKeyNotRegistered.
require.NoError(t, driverUser.RegisterUser(t.Context(), "100 wei"))
require.NoError(t, driverAuth.RegisterUser(t.Context(), "100 wei"))

// User has some balance to report on.
require.NoError(t, driverUser.Deposit(t.Context(), "1 ETH", "", "100 wei"))

// Grant auth's on-chain address the DEFAULT_AUTHORITY role for this
// application — otherwise the Deanonymize submitRequest reverts with
// AuthorityNotAllowed.
suite.RegisterAuthority(appID)
// suite.RegisterAuthority grants the *in-process authority* (the key
// baked into the fullstack suite's InProcessAuthority) the
// default-authority role, but our driverAuth is a different address.
// Grant it too via the lower-level helper.
sim := suite.GetSimTestHelper()
sim.WaitMined(sim.AddAuthority(new(big.Int).SetUint64(uint64(appID)), driverAuth.UserAddress()))

// Auth submits the Deanonymize request. Default report type = "balances".
reportID, err := driverAuth.RequestReport(t.Context(), "balances", "100 wei")
require.NoError(t, err)

// Report is now persisted to the manager's reports dir (the same path
// the in-process authority service reads from — configured at suite
// setup) and the subgraph has the RequestCompleted entry for the
// /getreport endpoint's sanity check. Pull it down via the full HTTP +
// decrypt pipeline.
reportIDHex := fmt.Sprintf("%x", reportID[:])
t.Logf("deanonymization request id: %s", reportIDHex)
destPath := filepath.Join(t.TempDir(), "report.json")
require.NoError(t, driverAuth.DownloadReport(t.Context(), reportIDHex, destPath))

raw, err := os.ReadFile(destPath)
require.NoError(t, err)
require.NotEmpty(t, raw)

// The decrypted report's shape is what downloadreport writes:
// applicationId and requestId are string-encoded (their custom
// MarshalJSON), reportData is the raw payload (JSON or base64 fallback).
var got struct {
ApplicationID string `json:"applicationId"`
RequestID string `json:"requestId"`
ReportData json.RawMessage `json:"reportData"`
}
require.NoError(t, json.Unmarshal(raw, &got))

require.Equal(t, appID.String(), got.ApplicationID,
"decrypted report should reference the deployed app")
// RequestIdType.MarshalJSON adds "0x" prefix; our reportIDHex is the
// unprefixed form used as the downloadreport flag value.
require.Equal(t, "0x"+reportIDHex, got.RequestID,
"decrypted report should reference the request that produced it")
require.NotEmpty(t, got.ReportData, "reportData should be populated for a balances report")

// Balances reports include the user's address somewhere in the payload.
// Compare case-insensitively — the payment app emits lowercase hex while
// UserAddress() returns the EIP-55 checksum form.
require.Contains(t, strings.ToLower(string(got.ReportData)),
strings.ToLower(driverUser.UserAddress().Hex()),
"balances report should mention the depositing user; raw=%s", string(got.ReportData))

suite.AssertNoStateUpdateErrors(t)
t.Log("Real deanonymize /getreport round-trip verified")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main_test

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

"github.com/HorizenOfficial/vela-nova/payment-app/testhelpers"
walletTestutil "github.com/HorizenOfficial/vela-nova/wallet/testutil"
"github.com/HorizenOfficial/vela/pkg/testutil/fullstack"
"github.com/stretchr/testify/require"
)

// TestDeanonymizeRequestFromUnauthorizedAuthorityReverts proves that
// ProcessorEndpoint.submitRequest rejects a DEANONYMIZATION request from
// an account that is NOT on the application's authority allowlist. This
// is the gate that keeps user data confidential — any loosening here
// would let arbitrary accounts trigger deanonymization reports on apps
// they have no business seeing into.
//
// ## Revert path
//
// ProcessorEndpoint.sol submitRequest:
//
// } else if (requestType == Structs.RequestType.DEANONYMIZATION) {
// if (!authorityRegistry.checkAuthorityIsAllowed(applicationId, msg.sender)) {
// revert AuthorityNotAllowed();
// }
// }
//
// The check fires at submitRequest time — before the request ever enters
// the pending queue and before any manager/executor processing. A rogue
// authority's request never costs the protocol anything beyond the gas
// spent on the (reverting) submission.
//
// ## Fullstack-unique
//
// - Hardhat unit tests cover AuthorityRegistry.checkAuthorityIsAllowed
// in isolation against a unit-test contract.
// - Only fullstack proves the wiring: that ProcessorEndpoint.submitRequest
// actually consults the registry on the DEANONYMIZATION branch, that
// msg.sender is the value compared against the allowlist, and that
// the AuthorityNotAllowed selector surfaces through the Go unpacker.
//
// ## Design
//
// - Single driver (fresh user). NewWalletDriver auto-grants DEPLOYER_ROLE
// to let it deploy the app, but does NOT grant any authority role on
// the app's AuthorityRegistry entry. The allowlist is default-empty
// for a freshly-deployed app, so this driver is unambiguously
// unauthorized as an authority.
// - No call to suite.RegisterAuthority / sim.AddAuthority for this
// driver. Skipping those is the whole point — TestDeanonymize_RealRoundTrip
// is the happy-path counterpart that DOES grant the role; this is the
// negative path that proves the gate actually fires without the grant.
// - Caller registration (RegisterUser) is NOT required: the Deanonymize
// payload is encrypted to the TEE's pubkey (read from the contract),
// not to the caller's own P521 key. The authority check fires at the
// contract level regardless of caller-side registration state.
//
// ## Flow
//
// 1. Suite start + executor + manager.
// 2. Build driver (fresh user with DEPLOYER_ROLE, no authority role).
// 3. driver.DeployApp — the ONLY reason to do this is to create an app
// whose applicationStateRoots[appID] is non-zero; without this, the
// submitRequest call would hit InvalidApplicationId first and the
// test would be testing the wrong gate.
// 4. driver.RequestReport(...) — submits a DEANONYMIZATION request from
// an unauthorized account. Contract reverts with AuthorityNotAllowed.
// The wallet driver surfaces the error.
// 5. Assert the error string contains "AuthorityNotAllowed".
// 6. suite.AssertNoStateUpdateErrors(t) — the revert is at submitRequest
// time, so the request never enters the pending queue and the manager
// never attempts a stateUpdate for it. The defensive hook must stay
// clean.
func TestDeanonymizeRequestFromUnauthorizedAuthorityReverts(t *testing.T) {
if os.Getenv("CI_FLAG") != "" {
t.Skip("Skipping fullstack test in CI environment")
}

t.Setenv("MANAGER_ARTIFACTS_PATH", t.TempDir())

suite := fullstack.NewFullStackSystemTestSuite(t, "wasmtime-payment",
testhelpers.NewNetworkLogConfig(), testhelpers.NewNetworkLogConfig())
defer suite.Cleanup()

require.NoError(t, suite.StartExecutor())
require.NoError(t, suite.StartManager())

// Step 2: fresh driver. DEPLOYER_ROLE auto-granted; authority role
// explicitly NOT granted. This is the whole asymmetry.
driver := walletTestutil.NewWalletDriver(t, suite)

// Step 3: deploy so the app exists. Without this, submitRequest would
// hit InvalidApplicationId first (applicationStateRoots[appID] == 0),
// and the test would be testing the wrong gate.
wasmBytes := testhelpers.BuildAndLoadWasmModule(t)
wasmPath := filepath.Join(t.TempDir(), "payment_app.wasm")
require.NoError(t, os.WriteFile(wasmPath, wasmBytes, 0o644))

appID, err := driver.DeployApp(t.Context(), wasmPath, "100 wei", nil)
require.NoError(t, err)
require.NotZero(t, appID)

// Step 4 + 5: unauthorized deanonymize request. The contract's
// authorityRegistry.checkAuthorityIsAllowed returns false for this
// driver's address (never granted), so submitRequest reverts with
// AuthorityNotAllowed. The wallet driver surfaces the unpacker's
// error string.
_, err = driver.RequestReport(t.Context(), "balances", "100 wei")
require.Error(t, err, "deanonymize request from unauthorized account must fail at submitRequest time")
require.Contains(t, err.Error(), "AuthorityNotAllowed",
"expected AuthorityNotAllowed revert (ProcessorEndpoint's DEANONYMIZATION branch check); got: %v", err,
)

// Step 6: no stateUpdate was attempted — the revert rejected the
// request before it entered the pending queue, so the manager never
// processed anything for it. The wrapper's error channel must stay
// empty. A non-empty channel would indicate (a) the rejection leaked
// into the stateUpdate path somehow, or (b) some unrelated update
// hit an unexpected error during the preceding deploy.
suite.AssertNoStateUpdateErrors(t)

t.Logf("deanonymize request from unauthorized authority correctly rejected: %v", err)
}
Loading
Loading