mirror of
https://github.com/luxfi/cevm.git
synced 2026-10-30 17:00:54 +00:00
ABIVersion is 7, the go_bridge.h ABI in which ok says whether the result is the block's. The binding does not compile against a header that names another, and init reads the loaded library's gpu_abi_version() instead of panicking on it: against a library of another ABI, ExecuteBlock calls nothing and declines every block, and a result of another ABI is declined too, so the caller runs them on its own EVM. The cgo tests hold the library to that contract. ExecuteBlock passes no state, so the transfers a device runs here cost nothing: they run on a GPU lane to TxOK at 21000 gas each, or are declined there when the host has no device, and a CPU lane declines them. A transfer that costs anything, and a batch with code or calldata, is declined on every lane. The opcode, bytecode and gas-parity tests ran code through the entry, which runs none, and are gone; the block-context test runs transfers. A test that needs a device skips on a GPU lane that declines. Co-authored-by: Hanzo Dev <[email protected]>
186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
package cevm
|
|
|
|
import (
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
// Tests in this file run under both `cgo` and `!cgo` builds. They cover the
|
|
// parts of the API that don't depend on the C++ library: enum stringers,
|
|
// constant exports, and the empty-input fast-path that ExecuteBlock promises
|
|
// to return without error.
|
|
|
|
func TestBackendString(t *testing.T) {
|
|
tests := []struct {
|
|
b Backend
|
|
want string
|
|
}{
|
|
{CPUSequential, "cpu-sequential"},
|
|
{CPUParallel, "cpu-parallel"},
|
|
{GPUMetal, "gpu-metal"},
|
|
{GPUCUDA, "gpu-cuda"},
|
|
{Backend(99), "unknown(99)"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.b.String(); got != tt.want {
|
|
t.Errorf("Backend(%d).String() = %q, want %q", int(tt.b), got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTxStatusString(t *testing.T) {
|
|
tests := []struct {
|
|
s TxStatus
|
|
want string
|
|
}{
|
|
{TxOK, "ok"},
|
|
{TxReturn, "return"},
|
|
{TxRevert, "revert"},
|
|
{TxOOG, "oog"},
|
|
{TxError, "error"},
|
|
{TxCallNotSupported, "call-not-supported"},
|
|
{TxStatus(99), "status(99)"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := tt.s.String(); got != tt.want {
|
|
t.Errorf("TxStatus(%d).String() = %q, want %q", int(tt.s), got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVMID(t *testing.T) {
|
|
if got := VMID(); got != "cevm" {
|
|
t.Errorf("VMID() = %q, want %q", got, "cevm")
|
|
}
|
|
}
|
|
|
|
func TestPluginPath(t *testing.T) {
|
|
p := PluginPath()
|
|
if p == "" {
|
|
t.Skip("could not determine home directory")
|
|
}
|
|
const suffix = "work/luxcpp/evm/build/bin/cevm"
|
|
if len(p) < len(suffix) {
|
|
t.Fatalf("PluginPath() too short: %q", p)
|
|
}
|
|
got := p[len(p)-len(suffix):]
|
|
if got != suffix {
|
|
t.Errorf("PluginPath() suffix = %q, want %q", got, suffix)
|
|
}
|
|
}
|
|
|
|
func TestPluginExists(t *testing.T) {
|
|
// This test just verifies the function runs without panicking.
|
|
// Whether it returns true or false depends on the build environment.
|
|
_ = PluginExists()
|
|
}
|
|
|
|
// TestExecuteBlockEmpty must succeed on both cgo and nocgo paths: the API
|
|
// contract is "empty input ⇒ empty result, no error" regardless of build.
|
|
// It covers the bare call and both functional-option forms so every option
|
|
// path honours the empty-input fast-path.
|
|
func TestExecuteBlockEmpty(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
opts []BlockOption
|
|
}{
|
|
{"bare", nil},
|
|
{"with-threads", []BlockOption{WithThreads(0)}},
|
|
{"with-context", []BlockOption{WithThreads(0), WithContext(nil)}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := ExecuteBlock(CPUSequential, nil, tc.opts...)
|
|
if err != nil {
|
|
t.Fatalf("ExecuteBlock(nil) returned error: %v", err)
|
|
}
|
|
if result.ABIVersion != ABIVersion {
|
|
t.Errorf("ABIVersion = %d, want %d", result.ABIVersion, ABIVersion)
|
|
}
|
|
if result.TotalGas != 0 {
|
|
t.Errorf("expected 0 total gas for empty block, got %d", result.TotalGas)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAvailableBackends_NonEmpty: every build must expose at least one backend.
|
|
// Under nocgo, that's CPUSequential. Under cgo, it's whatever the loaded
|
|
// library reports — but never zero.
|
|
func TestAvailableBackends_NonEmpty(t *testing.T) {
|
|
bs := AvailableBackends()
|
|
if len(bs) == 0 {
|
|
t.Fatal("AvailableBackends() returned empty list")
|
|
}
|
|
}
|
|
|
|
func TestBackendName_NonEmpty(t *testing.T) {
|
|
for _, b := range []Backend{CPUSequential, CPUParallel, GPUMetal, GPUCUDA} {
|
|
if BackendName(b) == "" {
|
|
t.Errorf("BackendName(%d) is empty", int(b))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestHealth_AlwaysReturnsReports: Health() must never return nil. Under
|
|
// nocgo it returns a single non-OK report. Under cgo it returns one report
|
|
// per available backend.
|
|
func TestHealth_AlwaysReturnsReports(t *testing.T) {
|
|
reports := Health()
|
|
if len(reports) == 0 {
|
|
t.Fatal("Health() returned no reports — even nocgo must report status")
|
|
}
|
|
}
|
|
|
|
// TestABIVersion locks the ABIVersion constant so a careless bump is caught
|
|
// by code review. Update both this test and ABIVersion together when the
|
|
// C ABI surface changes.
|
|
func TestABIVersion(t *testing.T) {
|
|
const want uint32 = 7
|
|
if ABIVersion != want {
|
|
t.Errorf("ABIVersion = %d, want %d (update C-side EVM_GPU_ABI_VERSION in lockstep)",
|
|
ABIVersion, want)
|
|
}
|
|
}
|
|
|
|
// TestBlockContextLayout pins the Go BlockContext to go_bridge.h's
|
|
// CBlockContext: every offset, and the size. The C side static_asserts
|
|
// sizeof(evm::gpu::BlockContext) == sizeof(CBlockContext) in go_bridge.cpp,
|
|
// and cevm_cgo.go checks this struct's size against CBlockContext's at compile
|
|
// time; offsets are what a reorder changes, and only this test sees them in a
|
|
// build without the library.
|
|
//
|
|
// A change to any number below means the C side's CBlockContext must move in
|
|
// lockstep AND ABIVersion must be bumped on both sides.
|
|
func TestBlockContextLayout(t *testing.T) {
|
|
var c BlockContext
|
|
|
|
if got, want := unsafe.Sizeof(c), uintptr(392); got != want {
|
|
t.Errorf("sizeof(BlockContext) = %d, want %d", got, want)
|
|
}
|
|
|
|
for _, f := range []struct {
|
|
name string
|
|
off uintptr
|
|
want uintptr
|
|
}{
|
|
{"Origin", unsafe.Offsetof(c.Origin), 0},
|
|
{"GasPrice", unsafe.Offsetof(c.GasPrice), 24},
|
|
{"Timestamp", unsafe.Offsetof(c.Timestamp), 32},
|
|
{"Number", unsafe.Offsetof(c.Number), 40},
|
|
{"Prevrandao", unsafe.Offsetof(c.Prevrandao), 48},
|
|
{"GasLimit", unsafe.Offsetof(c.GasLimit), 80},
|
|
{"ChainID", unsafe.Offsetof(c.ChainID), 88},
|
|
{"BaseFee", unsafe.Offsetof(c.BaseFee), 96},
|
|
{"BlobBaseFee", unsafe.Offsetof(c.BlobBaseFee), 104},
|
|
{"Coinbase", unsafe.Offsetof(c.Coinbase), 112},
|
|
{"BlobHashes", unsafe.Offsetof(c.BlobHashes), 132},
|
|
{"NumBlobHashes", unsafe.Offsetof(c.NumBlobHashes), 388},
|
|
} {
|
|
if f.off != f.want {
|
|
t.Errorf("offsetof(BlockContext.%s) = %d, want %d", f.name, f.off, f.want)
|
|
}
|
|
}
|
|
}
|