Skip to content
Merged
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
46 changes: 46 additions & 0 deletions cmd/wallet/zz_more_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"path/filepath"
"strings"
"sync"
"testing"
)

func TestOpenConns_ReturnsSentinel(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
if got := openConns(&wg); got != -1 {
t.Errorf("openConns = %d, want -1", got)
}
}

func TestOpenStore_HappyPath(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "ledger.db")
store, err := openStore(path)
if err != nil {
t.Fatalf("openStore: %v", err)
}
t.Cleanup(func() { _ = store.Close() })
}

func TestOpenStore_MkdirFails(t *testing.T) {
t.Parallel()
// Under /dev/null the parent mkdir fails.
if _, err := openStore("/dev/null/cannot/x/db.sqlite"); err == nil {
t.Error("expected mkdir error")
}
}

func TestDefaultPath_IncludesAppsRoot(t *testing.T) {
t.Parallel()
got := defaultPath("test-wallet")
if !strings.Contains(got, "io.pilot.wallet") {
t.Errorf("path = %q, want io.pilot.wallet substring", got)
}
if !strings.HasSuffix(got, "test-wallet") {
t.Errorf("path = %q, want suffix test-wallet", got)
}
}
68 changes: 68 additions & 0 deletions pkg/evm/zz_accessor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package evm

import (
"testing"
)

// TestEVMMethod_AccessorsCoverIDChainTokenAddress hits the four tiny
// accessor functions on *EVMMethod that don't go through Satisfy/Verify
// and previously had 0% coverage.
func TestEVMMethod_AccessorsCoverIDChainTokenAddress(t *testing.T) {
t.Parallel()
s, err := NewEVMSigner()
if err != nil {
t.Fatalf("NewEVMSigner: %v", err)
}
m, err := NewEVMMethod(s, ChainBaseSepolia, nil)
if err != nil {
t.Fatalf("NewEVMMethod: %v", err)
}
if got := m.ID(); got != EVMMethodID {
t.Errorf("ID = %q, want %q", got, EVMMethodID)
}
if got := m.ChainID(); got != ChainBaseSepolia {
t.Errorf("ChainID = %d, want %d", got, ChainBaseSepolia)
}
zero := Address{}
if m.Token() == zero {
t.Error("Token is zero — expected canonical USDC default for chain")
}
if m.Address() == zero {
t.Error("Address is zero")
}
}

// TestClient_EndpointReturnsConfigured covers the Endpoint accessor
// previously at 0%.
func TestClient_EndpointReturnsConfigured(t *testing.T) {
t.Parallel()
c := NewClient("https://example.invalid/rpc", nil)
if c.Endpoint() != "https://example.invalid/rpc" {
t.Errorf("Endpoint = %q", c.Endpoint())
}
}

// TestNewClient_NilHTTPClientGetsDefault covers the nil-client branch.
func TestNewClient_NilHTTPClientGetsDefault(t *testing.T) {
t.Parallel()
c := NewClient("https://example.invalid", nil)
if c.http == nil {
t.Error("http client should be non-nil after NewClient(_, nil)")
}
}

// TestUint128_PacksHiLo covers the Uint128 helper used in EIP-3009
// encoding — previously 0% because no test ever called it.
func TestUint128_PacksHiLo(t *testing.T) {
t.Parallel()
// hi=0, lo=1 → 1
if got := Uint128(0, 1); got.Uint64() != 1 {
t.Errorf("Uint128(0,1) = %s, want 1", got.String())
}
// hi=1, lo=0 → 2^64
got := Uint128(1, 0)
want := "18446744073709551616" // 2^64
if got.String() != want {
t.Errorf("Uint128(1,0) = %s, want %s", got.String(), want)
}
}
71 changes: 71 additions & 0 deletions pkg/evm/zz_eip55_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

package evm

// Regression for the typo-loss vector: ParseAddress accepts any 20-byte
// hex without checksum validation. A user copy-pasting an EVM address
// with a one-character typo (e.g., from a phishing site, or just human
// error) would have it silently accepted and lose funds on send.
//
// Fix: EIP-55 checksum validation. Mixed-case input MUST match the
// EIP-55 canonical form. All-lowercase and all-uppercase remain
// accepted (they convey no checksum information — wallets that don't
// emit checksummed addresses can still interoperate).

import (
"strings"
"testing"
)

func TestParseAddressRejectsBadEIP55Checksum(t *testing.T) {
t.Parallel()

// Vitalik's well-known address. The canonical EIP-55 form is:
// 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
// We tamper with ONE letter's case (the leading 'd' should be 'd',
// flip it to 'D') to produce an invalid checksum. Bytes are
// identical to the canonical form; only the case is wrong.
const canonical = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
tampered := "0x" + "D" + canonical[3:]

// Canonical: must accept.
if _, err := ParseAddress(canonical); err != nil {
t.Fatalf("canonical EIP-55 address rejected: %v", err)
}

// Tampered: must reject.
_, err := ParseAddress(tampered)
if err == nil {
t.Fatalf("tampered checksum accepted — typo-loss vector still open. Input: %q", tampered)
}
if !strings.Contains(err.Error(), "checksum") {
t.Errorf("expected error to mention 'checksum', got: %v", err)
}
}

func TestParseAddressAcceptsAllLowerOrAllUpper(t *testing.T) {
t.Parallel()
// Strings with NO case mix carry no checksum signal — accept them.
// This preserves compatibility with wallets that don't emit
// checksummed addresses (older clients, some hardware wallets).

cases := []string{
"0xd8da6bf26964af9d7eed9e03e53415d37aa96045", // all lower
"0xD8DA6BF26964AF9D7EED9E03E53415D37AA96045", // all upper
}
for _, in := range cases {
if _, err := ParseAddress(in); err != nil {
t.Errorf("ParseAddress(%q) returned error: %v", in, err)
}
}
}

func TestParseAddressNoPrefix(t *testing.T) {
t.Parallel()
// Bare hex (no 0x prefix) preserved for backwards-compat. Mixed
// case STILL gets checksum-checked if present.

if _, err := ParseAddress("d8da6bf26964af9d7eed9e03e53415d37aa96045"); err != nil {
t.Errorf("bare-hex lowercase rejected: %v", err)
}
}
50 changes: 50 additions & 0 deletions pkg/evm/zz_more_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package evm

import (
"errors"
"testing"
)

func TestErrUnknownChain_ChainID(t *testing.T) {
t.Parallel()
err := errChainUnknown(31337)
if err.Error() == "" {
t.Error("error message empty")
}
var uc errUnknownChain
if !errors.As(err, &uc) {
t.Fatal("errors.As failed")
}
if uc.ChainID() != 31337 {
t.Errorf("ChainID = %d, want 31337", uc.ChainID())
}
}

func TestEVMSigner_PublicKey(t *testing.T) {
t.Parallel()
s, err := NewEVMSigner()
if err != nil {
t.Fatalf("NewEVMSigner: %v", err)
}
pub := s.PublicKey()
if len(pub) != 65 {
t.Errorf("PublicKey len = %d, want 65 (uncompressed)", len(pub))
}
if pub[0] != 0x04 {
t.Errorf("PublicKey[0] = %#x, want 0x04 prefix", pub[0])
}
}

func TestEVMSigner_AddressAndPrivateKeyBytes(t *testing.T) {
t.Parallel()
s, err := NewEVMSigner()
if err != nil {
t.Fatalf("NewEVMSigner: %v", err)
}
if (s.Address() == Address{}) {
t.Error("Address is zero")
}
if len(s.PrivateKeyBytes()) != 32 {
t.Errorf("PrivateKeyBytes len = %d, want 32", len(s.PrivateKeyBytes()))
}
}
Loading
Loading