mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
Lux Partners Limited is not the entity behind this work. Every place the old name stood — copyright lines, site footers, brand config, legal and compliance text — now names the company that actually holds it. lux.fund is the exception and is not renamed to match: that site belongs to Lux Assets Management LLC, a different company, and it says so. Two kinds of line were left for a person rather than rewritten. A dated news item recording that Lux Partners Limited was established in the Isle of Man is a historical fact, and renaming it would falsify the record. And a sentence asserting that the company is domiciled in the Isle of Man is a claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle of Man company, so swapping the name there would assert something untrue. Co-authored-by: Hanzo Dev <[email protected]>
183 lines
5.2 KiB
Go
183 lines
5.2 KiB
Go
// Copyright (C) 2025, Lux Industries Inc All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package evm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/luxfi/crypto"
|
|
"github.com/luxfi/evm/rpc"
|
|
)
|
|
|
|
var ErrUnknownErrorSelector = fmt.Errorf("unknown error selector")
|
|
|
|
// also used at mocks
|
|
var (
|
|
rpcDialContext = rpc.DialContext
|
|
)
|
|
|
|
// RawClient wraps over rpc.Client for calls used by SDK. Used to make evm calls not available in ethclient:
|
|
// - debug trace call
|
|
// - debug trace transaction
|
|
// Features:
|
|
// - finds out url scheme in case it is missing, to connect to ws/wss/http/https
|
|
// - repeats to try to recover from failures, generating its own context for each call
|
|
// - logs rpc url in case of failure
|
|
type RawClient struct {
|
|
RPCClient *rpc.Client
|
|
URL string
|
|
// also used at mocks
|
|
CallContext func(context.Context, interface{}, string, ...interface{}) error
|
|
}
|
|
|
|
// GetRawClient connects a raw evm rpc client to the given [rpcURL].
|
|
// Supports [repeatsOnFailure] failures.
|
|
func GetRawClient(rpcURL string) (RawClient, error) {
|
|
client := RawClient{
|
|
URL: rpcURL,
|
|
}
|
|
hasScheme, err := HasScheme(rpcURL)
|
|
if err != nil {
|
|
return RawClient{}, err
|
|
}
|
|
client.RPCClient, err = retryWithContextGen(
|
|
apiLargeContext,
|
|
func(ctx context.Context) (*rpc.Client, error) {
|
|
if hasScheme {
|
|
return rpcDialContext(ctx, rpcURL)
|
|
} else {
|
|
_, scheme, err := GetClientWithoutScheme(rpcURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rpcDialContext(ctx, scheme+rpcURL)
|
|
}
|
|
},
|
|
repeatsOnFailure,
|
|
sleepBetweenRepeats,
|
|
)
|
|
if err != nil {
|
|
err = fmt.Errorf("failure connecting to rpc client on %s: %w", rpcURL, err)
|
|
}
|
|
client.CallContext = client.RPCClient.CallContext
|
|
return client, err
|
|
}
|
|
|
|
// Close closes underlying rpc connection.
|
|
func (client RawClient) Close() {
|
|
client.RPCClient.Close()
|
|
}
|
|
|
|
// DebugTraceTransaction returns a trace for the given [txID] on [client].
|
|
// Supports [repeatsOnFailure] failures.
|
|
func (client RawClient) DebugTraceTransaction(
|
|
txID string,
|
|
) (map[string]interface{}, error) {
|
|
trace, err := retryWithContextGen(
|
|
apiLargeContext,
|
|
func(ctx context.Context) (map[string]interface{}, error) {
|
|
var trace map[string]interface{}
|
|
err := client.CallContext(
|
|
ctx,
|
|
&trace,
|
|
"debug_traceTransaction",
|
|
txID,
|
|
map[string]string{"tracer": "callTracer"},
|
|
)
|
|
return trace, err
|
|
},
|
|
repeatsOnFailure,
|
|
sleepBetweenRepeats,
|
|
)
|
|
if err != nil {
|
|
err = fmt.Errorf("failure tracing tx %s on %s: %w", txID, client.URL, err)
|
|
}
|
|
return trace, err
|
|
}
|
|
|
|
// DebugTraceCall returns a trace for making a call on [client] with the given [data].
|
|
// Supports [repeatsOnFailure] failures.
|
|
func (client RawClient) DebugTraceCall(
|
|
data map[string]string,
|
|
) (map[string]interface{}, error) {
|
|
trace, err := retryWithContextGen(
|
|
apiLargeContext,
|
|
func(ctx context.Context) (map[string]interface{}, error) {
|
|
var trace map[string]interface{}
|
|
err := client.CallContext(
|
|
ctx,
|
|
&trace,
|
|
"debug_traceCall",
|
|
data,
|
|
"latest",
|
|
map[string]interface{}{
|
|
"tracer": "callTracer",
|
|
"tracerConfig": map[string]interface{}{
|
|
"onlyTopCall": false,
|
|
},
|
|
},
|
|
)
|
|
return trace, err
|
|
},
|
|
repeatsOnFailure,
|
|
sleepBetweenRepeats,
|
|
)
|
|
if err != nil {
|
|
err = fmt.Errorf("failure tracing call on %s: %w", client.URL, err)
|
|
}
|
|
return trace, err
|
|
}
|
|
|
|
// GetTxTrace returns a trace for the given [txID] on [rpcURL].
|
|
// Supports [repeatsOnFailure] failures.
|
|
func GetTxTrace(rpcURL string, txID string) (map[string]interface{}, error) {
|
|
client, err := GetRawClient(rpcURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return client.DebugTraceTransaction(txID)
|
|
}
|
|
|
|
// GetFunctionSelector returns evm function selector code for the given function signature.
|
|
// EVM maps function and error signatures into codes that are then used in traces.
|
|
func GetFunctionSelector(functionSignature string) string {
|
|
return "0x" + hex.EncodeToString(crypto.Keccak256([]byte(functionSignature))[:4])
|
|
}
|
|
|
|
// GetErrorFromTrace returns golang error associated with [trace] by using [functionSignatureToError]
|
|
// to map function signatures to evm function selectors in [trace], and then to golang errors.
|
|
// First returned error is the mapped error, second error is for errors obtained
|
|
// executing this function.
|
|
func GetErrorFromTrace(
|
|
trace map[string]interface{},
|
|
functionSignatureToError map[string]error,
|
|
) (error, error) {
|
|
traceOutputI, ok := trace["output"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("trace does not contain output field")
|
|
}
|
|
traceOutput, ok := traceOutputI.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected type string for trace output, got %T", traceOutputI)
|
|
}
|
|
traceOutputBytes, err := hex.DecodeString(strings.TrimPrefix(traceOutput, "0x"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failure decoding trace output: %w", err)
|
|
}
|
|
if len(traceOutputBytes) < 4 {
|
|
return nil, fmt.Errorf("less than 4 bytes in trace output")
|
|
}
|
|
traceErrorSelector := "0x" + hex.EncodeToString(traceOutputBytes[:4])
|
|
for errorSignature, err := range functionSignatureToError {
|
|
errorSelector := GetFunctionSelector(errorSignature)
|
|
if traceErrorSelector == errorSelector {
|
|
return err, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("%w: %s", ErrUnknownErrorSelector, traceErrorSelector)
|
|
}
|