Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
79ecef0
feat(test-suite): implement the delegated user decryption test cases
isaacdecoded Jan 19, 2026
3122465
chore(common): sync with main
isaacdecoded Jan 20, 2026
833655f
feat(test-suite): implement test cases for delegated user decryptions
isaacdecoded Jan 21, 2026
2695c85
refactor(test-suite): adjust paused gateway tests and e2e test ci wor…
isaacdecoded Jan 22, 2026
65fdae7
refactor(test-suite): implement more realistic delegated user decrypt…
isaacdecoded Jan 26, 2026
28d34cf
refactor(test-suite): rename test contract for delegated user decrypt…
isaacdecoded Jan 29, 2026
77c5c4e
chore(common): sync with main
isaacdecoded Jan 29, 2026
350edd7
chore(test-suite): replace test-suite version
isaacdecoded Jan 29, 2026
e9821bb
fix(test-suite): adjust expected error message
isaacdecoded Jan 29, 2026
3dfd1b1
chore(test-suite): replace test-suite version
isaacdecoded Jan 29, 2026
de79733
chore(common): sync with main
isaacdecoded Jan 30, 2026
96ff2a2
chore(test-suite): replace test-suite version
isaacdecoded Jan 30, 2026
8ab8c83
fix(test-suite): add smoke runner hardening
Eikix Jan 27, 2026
3fde255
fix(test-suite): document smoke env setup
Eikix Jan 27, 2026
ce9e49c
fix(test-suite): simplify env handling + post-success cleanup
Eikix Jan 27, 2026
ad8c036
style: apply prettier formatting
Eikix Jan 27, 2026
f094d9a
feat(test-suite): add BetterStack heartbeat integration
Eikix Jan 27, 2026
b1cedc6
fix(test-suite): use receipt.contractAddress instead of computing it
Eikix Jan 27, 2026
d000a5e
fix(test-suite): doc and deprecated API cleanup
Eikix Jan 27, 2026
c93f181
fix(test-suite): remove unnecessary hardhat chainId check and redunda…
Eikix Jan 27, 2026
0739fe2
fix(test-suite): add robustness for mainnet API key, decryption timeo…
Eikix Jan 27, 2026
4825f0b
fix(test-suite): log full error on gas estimation failure for debugging
Eikix Jan 27, 2026
af54f37
refactor(test-suite): delegate coprocessor config entirely to ZamaConfig
Eikix Jan 27, 2026
6296b74
refactor(test-suite): simplify RPC URL config and require network-spe…
Eikix Jan 27, 2026
018fcc2
refactor(test-suite): simplify instance.ts config - remove unnecessar…
Eikix Jan 27, 2026
b457fec
refactor(test-suite): use BetterStack /fail endpoint for failure repo…
Eikix Jan 27, 2026
933e689
refactor(test-suite): simplify env var parsing in smoke runner
Eikix Jan 27, 2026
07f3bb6
refactor(test-suite): address PR feedback for smoke runner
Eikix Jan 29, 2026
d48bd0e
feat(test-suite): add timing breakdown to smoke heartbeat
Eikix Jan 29, 2026
9c398a4
fix(test-suite): lazy validation for network RPC URLs
Eikix Jan 29, 2026
a314d99
feat(test-suite): add RPC_URL fallback for sepolia/mainnet
Eikix Jan 29, 2026
2491e32
docs(test-suite): clarify env setup for sepolia/mainnet/devnet
Eikix Jan 29, 2026
78d6f50
fix(test-suite): provide placeholder URLs for sepolia/mainnet during …
Eikix Jan 29, 2026
fdbeaf0
refactor(test-suite): move TestInput.sol to contracts/smoke/
Eikix Jan 29, 2026
4458e47
feat(test-suite): improve smoke signer handling and observability
Eikix Jan 29, 2026
433c27b
fix(test-suite): log warning when failure heartbeat fails
Eikix Jan 30, 2026
780077b
revert(test-suite): restore hardcoded addresses in E2ECoprocessorConf…
Eikix Jan 30, 2026
b3ce061
feat(test-suite): inject coprocessor config via constructor for smoke…
Eikix Jan 30, 2026
051cc77
refactor(test-suite): split SmokeTestInput from TestInput to avoid E2…
Eikix Jan 30, 2026
536ad37
fix(test-suite): handle late tx confirmation in retry loop
Eikix Jan 30, 2026
538c80f
refactor(test-suite): use tx.wait() with TRANSACTION_REPLACED handling
Eikix Jan 30, 2026
b885095
fix(test-suite): handle ethers v6 tx.wait() errors correctly
Eikix Jan 30, 2026
452d29c
fix(test-suite): add retry logic for hardhat compile in Docker build
Eikix Jan 30, 2026
41e97d4
Revert "fix(test-suite): add retry logic for hardhat compile in Docke…
Eikix Jan 30, 2026
76dcb5e
Merge of #1808
mergify[bot] Jan 30, 2026
5b5c8c8
Merge of #1840
mergify[bot] Jan 30, 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
10 changes: 5 additions & 5 deletions .github/workflows/test-suite-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ jobs:
run: |
./fhevm-cli test user-decryption

- name: Delegated User Decryption test
working-directory: test-suite/fhevm
run: |
./fhevm-cli test delegated-user-decryption

- name: ERC20 test
working-directory: test-suite/fhevm
run: |
Expand All @@ -205,11 +210,6 @@ jobs:
run: |
./fhevm-cli test public-decrypt-http-mixed

- name: Delegate User Decryption (partial test)
working-directory: test-suite/fhevm
run: |
./fhevm-cli test delegate-user-decryption

- name: Random operators test (subset)
working-directory: test-suite/fhevm
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ logs/
.env.*
!.env.example
!.env.sample
# Allow shared env templates in subprojects
!test-suite/e2e/.env.devnet
# If you have .env files specific to subprojects, e.g. subproject/.env,
# the .env rule above will catch them.

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions test-suite/e2e/.env.devnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# CONTRACT ADDRESS OVERRIDE CONFIGURATION
#
# If the runtime Hardhat network selected via the --network flag (e.g., 'devnet')
# matches the value defined here, all contract addresses and URLs below
# will OVERRIDE any default or previously loaded configuration.
FHEVM_HARDHAT_NETWORK="devnet"

# Gateway
INPUT_VERIFICATION_ADDRESS="0xf091D9B4C2da7ecd11858cDD1F4515a8a767D755"
DECRYPTION_ADDRESS="0xA4dc265D54D25D41565c60d36097E8955B03decD"
# Host
ACL_CONTRACT_ADDRESS="0xBCA6F8De823a399Dc431930FD5EE550Bf1C0013e"
KMS_VERIFIER_CONTRACT_ADDRESS="0x3F3819BeBE4bD0EFEf8078Df6f9B574ADa80CCA4"
INPUT_VERIFIER_CONTRACT_ADDRESS="0x6B32f47E39B0F8bE8bEAD5B8990F62E3e28ac08d"
FHEVM_EXECUTOR_CONTRACT_ADDRESS="0x5cc8c5A366E733d4f1e677B2A9C08Bc2ea49b302"
HCU_LIMIT_CONTRACT_ADDRESS="0xb8E70273De41498aAF047fd73ae1F3025D8709f8"
RELAYER_URL="https://relayer.dev.zama.cloud"

# Hardhat account seed (used for hardhat/anvil/sepolia)
MNEMONIC="test test test test test test test test test test test junk"

# Provider keys
INFURA_API_KEY=""
ETHERSCAN_API_KEY=""

# Required: authorized caller addresses for Numeric deploy (comma-separated list)
AUTHORIZED_CALLER_ADDRESSES=""

# Optional: set a specific payment token for SimplifiedAuction deploys
SIMPLIFIED_AUCTION_PAYMENT_TOKEN=""
13 changes: 13 additions & 0 deletions test-suite/e2e/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
MNEMONIC="adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer"
RPC_URL=""
SEPOLIA_ETH_RPC_URL=""
MAINNET_ETH_RPC_URL=""
RELAYER_URL=""
ZAMA_FHEVM_API_KEY=""
# Gateway
CHAIN_ID_GATEWAY="54321"
CHAIN_ID_HOST="12345"
Expand All @@ -9,5 +14,13 @@ ACL_CONTRACT_ADDRESS="0x05fD9B5EFE0a996095f42Ed7e77c390810CF660c"
KMS_VERIFIER_CONTRACT_ADDRESS="0xcCAe95fF1d11656358E782570dF0418F59fA40e1"
INPUT_VERIFIER_CONTRACT_ADDRESS="0xa1880e99d86F081E8D3868A8C4732C8f65dfdB11"
FHEVM_EXECUTOR_CONTRACT_ADDRESS="0x12B064FB845C1cc05e9493856a1D637a73e944bE"
TEST_INPUT_CONTRACT_ADDRESS=""

HARDHAT_NETWORK="staging"

# Smoke runner (optional)
BETTERSTACK_HEARTBEAT_URL=""
# SMOKE_DEPLOY_CONTRACT=1
# SMOKE_RUN_TESTS=1
# SMOKE_TX_TIMEOUT_SECS=48
# SMOKE_DECRYPT_TIMEOUT_SECS=120
74 changes: 74 additions & 0 deletions test-suite/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,77 @@ REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat ignition deploy ./ignition/modules/Lock.ts
```

## Smoke runner (inputFlow)

Runs a single on-chain smoke flow (input + add42 + decrypt) using Hardhat as a runtime
and hardened transaction handling.

### Prereqs

**For sepolia/mainnet**, most config is auto-populated from the SDK (`SepoliaConfig`/`MainnetConfig`).
You only need:

- `RPC_URL` (or `SEPOLIA_ETH_RPC_URL` / `MAINNET_ETH_RPC_URL`)
- `MNEMONIC`
- `ZAMA_FHEVM_API_KEY` (mainnet only)

**For devnet**, use the pre-configured `.env.devnet` (all addresses included):

```shell
DOTENV_CONFIG_PATH=./.env.devnet npx hardhat run --network devnet scripts/smoke-inputflow.ts
```

**For other networks** (staging, custom), set all variables manually - see `.env.example`.

Network-specific RPC URLs:

- staging/zwsDev: `RPC_URL` (defaults to localhost:8545)
- sepolia: `SEPOLIA_ETH_RPC_URL` (falls back to `RPC_URL`)
- mainnet: `MAINNET_ETH_RPC_URL` (falls back to `RPC_URL`)

For pod deployments, just set `RPC_URL` - it works for all networks.

Set `TEST_INPUT_CONTRACT_ADDRESS` to reuse an existing contract (requires `SMOKE_DEPLOY_CONTRACT=0`).

Hardhat loads env from `test-suite/e2e/.env` by default; override with `DOTENV_CONFIG_PATH`.
You can also store secrets with Hardhat vars, e.g. `npx hardhat vars set SEPOLIA_ETH_RPC_URL` (it will prompt for the value).
For devnet, `test-suite/e2e/.env.devnet` provides a ready baseline (use `DOTENV_CONFIG_PATH=./.env.devnet`).

### Signer configuration

The smoke runner uses HD wallet signers derived from `MNEMONIC`. By default, it uses indices `0,1,2`
for automatic failover - if one signer has a stuck transaction, it falls back to another.

**Important:** All configured signers should be funded for maximum resilience. The script logs all
available signers at startup with their balances and warns if any have low balance (< 0.1 ETH).

To derive signer addresses from a mnemonic (for funding):
```shell
# Using Foundry's cast
cast wallet address --mnemonic "your mnemonic here" --mnemonic-index 0
cast wallet address --mnemonic "your mnemonic here" --mnemonic-index 1
cast wallet address --mnemonic "your mnemonic here" --mnemonic-index 2
```

### Smoke-specific knobs (defaults in parentheses)

- `SMOKE_SIGNER_INDICES` (`0,1,2`) - comma-separated list of signer indices to use for failover
- `SMOKE_TX_TIMEOUT_SECS` (`48`)
- `SMOKE_TX_MAX_RETRIES` (`2`)
- `SMOKE_FEE_BUMP` (`1.125^4`)
- `SMOKE_MAX_BACKLOG` (`3`)
- `SMOKE_CANCEL_BACKLOG` (`1`) - set to `0` to disable auto-cancel of pending transactions
- `SMOKE_DEPLOY_CONTRACT` (`1`) - set to `0` to attach to existing contract via `TEST_INPUT_CONTRACT_ADDRESS`
- `SMOKE_RUN_TESTS` (`1`) - set to `0` to deploy contract only without running tests
- `SMOKE_DECRYPT_TIMEOUT_SECS` (`120`) - timeout for decryption operations
- `BETTERSTACK_HEARTBEAT_URL` (optional) - if set, pings BetterStack on success; reports error with exit code on failure

### Run

```shell
cd test-suite/e2e
npx hardhat run --network zwsDev scripts/smoke-inputflow.ts
npx hardhat run --network sepolia scripts/smoke-inputflow.ts
npx hardhat run --network mainnet scripts/smoke-inputflow.ts
```
11 changes: 0 additions & 11 deletions test-suite/e2e/contracts/DelegateUserDecryptDelegate.sol

This file was deleted.

28 changes: 0 additions & 28 deletions test-suite/e2e/contracts/DelegateUserDecryptDelegator.sol

This file was deleted.

2 changes: 2 additions & 0 deletions test-suite/e2e/contracts/E2ECoprocessorConfigLocal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.8.24;
import {CoprocessorConfig, FHE} from "@fhevm/solidity/lib/FHE.sol";

library DefaultCoprocessorConfig {
/// @dev These addresses are placeholders. They are patched at runtime via sed
/// in the E2E test runner script with the actual deployment addresses.
function getConfig() internal pure returns (CoprocessorConfig memory) {
return
CoprocessorConfig({
Expand Down
73 changes: 73 additions & 0 deletions test-suite/e2e/contracts/SmartWalletWithDelegation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;

import "@fhevm/solidity/lib/FHE.sol";
import {E2ECoprocessorConfig} from "./E2ECoprocessorConfigLocal.sol";

/// @notice SmartWallet contract that supports delegated user decryption.
contract SmartWalletWithDelegation is E2ECoprocessorConfig {
struct Transaction {
address target;
bytes data;
}

event ProposedTx(uint256 indexed txId, address target, bytes data);

uint256 public txCounter;
address public owner;
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => bool) public executed;

constructor(address _owner) {
require(_owner != address(0), "Owner cannot be zero address");
owner = _owner;
}

modifier onlyOwner() {
require(msg.sender == owner, "Sender is not the owner");
_;
}

/// @notice Propose a transaction and assume as approved (since there's only one owner).
/// @param target The target contract address
/// @param data The calldata to execute
function proposeTx(address target, bytes calldata data) external onlyOwner returns (uint256) {
txCounter++;
uint256 txId = txCounter;
transactions[txCounter] = Transaction({target: target, data: data});
emit ProposedTx(txId, target, data);
return txId;
}

/// @notice Execute a previously proposed transaction.
/// @param txId The transaction ID to execute.
function executeTx(uint256 txId) external onlyOwner {
require(txId != 0 && txId <= txCounter, "Invalid txId");
require(!executed[txId], "tx has already been executed");
Transaction memory transaction = transactions[txId];

(bool success, ) = (transaction.target).call(transaction.data);
require(success, "tx reverted");
executed[txId] = true;
}

/// @notice Delegate user decryption for a specific contract.
/// @dev This allows an EOA to decrypt confidential data owned by this smart wallet.
/// @param delegate The address that will be able to user decrypt.
/// @param delegateContractAddress The contract address for which delegation applies.
/// @param expirationTimestamp When the delegation expires.
function delegateUserDecryption(
address delegate,
address delegateContractAddress,
uint64 expirationTimestamp
) external onlyOwner {
FHE.delegateUserDecryption(delegate, delegateContractAddress, expirationTimestamp);
}

/// @notice Revoke a previously granted delegation.
/// @param delegate The address to revoke delegation from.
/// @param delegateContractAddress The contract address for which to revoke delegation.
function revokeUserDecryptionDelegation(address delegate, address delegateContractAddress) external onlyOwner {
FHE.revokeUserDecryptionDelegation(delegate, delegateContractAddress);
}
}
39 changes: 39 additions & 0 deletions test-suite/e2e/contracts/smoke/SmokeTestInput.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity ^0.8.24;

import "@fhevm/solidity/lib/FHE.sol";
import {CoprocessorConfig} from "@fhevm/solidity/lib/Impl.sol";

/// @notice Smoke test contract with constructor-based coprocessor config injection.
/// @dev Unlike TestInput.sol which uses E2ECoprocessorConfig (sed-patched at runtime),
/// this contract receives addresses at deploy time, enabling smoke tests to run on
/// devnet, sepolia, and mainnet without sed patching.
contract SmokeTestInput {
euint64 public resUint64;

constructor(address aclAddress, address coprocessorAddress, address kmsVerifierAddress) {
FHE.setCoprocessor(
CoprocessorConfig({
ACLAddress: aclAddress,
CoprocessorAddress: coprocessorAddress,
KMSVerifierAddress: kmsVerifierAddress
})
);
}

function requestUint64NonTrivial(externalEuint64 inputHandle, bytes calldata inputProof) public {
resUint64 = FHE.fromExternal(inputHandle, inputProof);
FHE.allowThis(resUint64);
}

// Adds a trivially-encrypted 42 to the user-provided encrypted uint64 input.
function add42ToInput64(externalEuint64 inputHandle, bytes calldata inputProof) public {
euint64 input = FHE.fromExternal(inputHandle, inputProof);
euint64 trivial42 = FHE.asEuint64(42);
resUint64 = FHE.add(input, trivial42);
FHE.allowThis(resUint64);
FHE.allow(resUint64, msg.sender);
FHE.makePubliclyDecryptable(resUint64);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pragma solidity ^0.8.24;

import "@fhevm/solidity/lib/FHE.sol";
import {E2ECoprocessorConfig} from "./E2ECoprocessorConfigLocal.sol";
import {E2ECoprocessorConfig} from "../E2ECoprocessorConfigLocal.sol";

contract TestInput is E2ECoprocessorConfig {
euint64 public resUint64;
Expand Down
Loading