mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
Each of them tests its own, and each passes, which is the shape of the problem: a suite written against an implementation agrees with the implementation it was written against. Whether the three charge the same price and return the same bytes for the same call is a question none of them can ask from the inside. This is the Go side of that comparison and the corpus it writes: 235 calls, most borrowed from geth's own precompile test data on the reasoning that an input the reference tests itself against is one the other two should survive, the rest covering what that data does not reach — the three precompiles it ships no vectors for, an empty input to every address, addresses nobody serves, and the boundary between paying and not paying. Two things it already establishes about Go alone. At 0x0100 the stock table charges 6900 and the Lux secp256r1 module charges 3450, and the module is what answers, because LuxPrecompileOverrider is consulted before the standard table; the reference asks in that order, so the corpus records the price a chain actually charges. And address zero is not unclaimed: the dead-address module is registered there, reads chain state, and cannot be answered for without a chain — so it says that rather than being answered for, and rather than taking the run down, which is what a nil state did to it. `make precompiles` refuses to run with one implementation. That is correct and it is the next piece of work. Co-authored-by: Hanzo Dev <[email protected]>
111 lines
3.1 KiB
Go
111 lines
3.1 KiB
Go
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// A vector is one call: an address, what the caller was willing to pay, and
|
|
// the bytes. Nothing about what should happen — that is what the three
|
|
// implementations are for.
|
|
type Vector struct {
|
|
ID string
|
|
Address string // 20 bytes, lower-case hex, no leading 0x
|
|
Gas uint64
|
|
Input []byte
|
|
}
|
|
|
|
func (v Vector) Line() string {
|
|
in := "-"
|
|
if len(v.Input) > 0 {
|
|
in = hex(v.Input)
|
|
}
|
|
return fmt.Sprintf("V\t%s\t%s\t%d\t%s", v.ID, v.Address, v.Gas, in)
|
|
}
|
|
|
|
// The four things an implementation can say about a call.
|
|
//
|
|
// They are deliberately coarse. Two implementations that both refuse an input
|
|
// agree, whatever their messages say, and the message travels in the note
|
|
// where it explains without being compared — an error string is a fact about a
|
|
// codebase, not about a precompile.
|
|
const (
|
|
OK = "OK" // it ran; output is the bytes it returned
|
|
FAILED = "FAILED" // it read the input and refused it
|
|
OOG = "OOG" // the gas offered was below what it charges
|
|
ABSENT = "ABSENT" // nothing serves this address here
|
|
STATE = "STATE" // something serves it, and answering needs a chain
|
|
noBytes = "-" // an empty output, spelled so it is not an empty column
|
|
)
|
|
|
|
// A Result answers a vector. Gas is what was CHARGED, never what was left:
|
|
// the three implementations disagree about which of those they return, and a
|
|
// wire format that let each of them send its own convention would compare two
|
|
// different quantities and call the difference a bug.
|
|
//
|
|
// On OOG and ABSENT the charge is 0 by convention, because there is no charge
|
|
// to report and a number nobody agrees to means the column stops comparing.
|
|
type Result struct {
|
|
ID string
|
|
Status string
|
|
Gas uint64
|
|
Output []byte
|
|
Note string
|
|
}
|
|
|
|
func (r Result) Line() string {
|
|
out := noBytes
|
|
if len(r.Output) > 0 {
|
|
out = hex(r.Output)
|
|
}
|
|
return fmt.Sprintf("R\t%s\t%s\t%d\t%s\t%s", r.ID, r.Status, r.Gas, out, r.Note)
|
|
}
|
|
|
|
func hex(b []byte) string {
|
|
const digits = "0123456789abcdef"
|
|
s := make([]byte, 0, len(b)*2)
|
|
for _, c := range b {
|
|
s = append(s, digits[c>>4], digits[c&0x0f])
|
|
}
|
|
return string(s)
|
|
}
|
|
|
|
func unhex(s string) ([]byte, error) {
|
|
if s == noBytes {
|
|
return nil, nil
|
|
}
|
|
s = strings.TrimPrefix(s, "0x")
|
|
if len(s)%2 != 0 {
|
|
return nil, fmt.Errorf("odd-length hex: %d chars", len(s))
|
|
}
|
|
b := make([]byte, len(s)/2)
|
|
for i := range b {
|
|
n, err := strconv.ParseUint(s[i*2:i*2+2], 16, 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b[i] = byte(n)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// ParseVector reads one V line. The columns are id, address, gas, input.
|
|
func ParseVector(line string) (Vector, error) {
|
|
f := strings.Split(line, "\t")
|
|
if len(f) != 5 || f[0] != "V" {
|
|
return Vector{}, fmt.Errorf("not a vector line: %q", line)
|
|
}
|
|
gas, err := strconv.ParseUint(f[3], 10, 64)
|
|
if err != nil {
|
|
return Vector{}, fmt.Errorf("%s: gas: %w", f[1], err)
|
|
}
|
|
in, err := unhex(f[4])
|
|
if err != nil {
|
|
return Vector{}, fmt.Errorf("%s: input: %w", f[1], err)
|
|
}
|
|
return Vector{ID: f[1], Address: strings.ToLower(f[2]), Gas: gas, Input: in}, nil
|
|
}
|