mirror of
https://github.com/luxfi/chains.git
synced 2026-07-27 03:39:41 +00:00
The bridge is permissionless and non-custodial. Authoritative quote math and swap state must live where the validator quorum runs consensus — in this VM — not in any single daemon's local cache. This change moves both into bridgevm and exposes them via JSON-RPC so any client can reach the same settlement decisions. New RPC methods: bridge_estimateFee — authoritative quote computation bridge_submitRequest — create + quote-snapshot bridge_getStatus — read swap by id bridge_cancelRequest — idempotent cancel bridge_health — liveness probe bridge_getMPCPublicKey — threshold group public key Architecture: - QuoteEngine (quote.go) StaticPriceFeed → quorum oracle later - SwapStore (swap_store.go) in-memory genesis impl; block-persistence next - Service methods (rpc.go) JSON-RPC dispatch (snake + dot aliases) Attestation aggregation already lived in bridge_signing.go via BridgeSigner + BridgeMessageValidator — no changes needed there. LP-333 signer-set methods unchanged.
96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package bridgevm
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInMemorySwapStore_PutGet(t *testing.T) {
|
|
s := newInMemorySwapStore()
|
|
rec := &BridgeRequestRecord{
|
|
SourceChain: "ETHEREUM_SEPOLIA",
|
|
DestChain: "LUX_TESTNET",
|
|
SourceAsset: "ETH",
|
|
DestAsset: "LUX",
|
|
Amount: "1",
|
|
Recipient: "0xa28fAE14eB42e7A5C36Ad2D774a2b7Eb293c4473",
|
|
}
|
|
if err := s.Put(rec); err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
if !strings.HasPrefix(rec.RequestID, "req_") {
|
|
t.Errorf("RequestID = %q, want req_ prefix", rec.RequestID)
|
|
}
|
|
if rec.Status != StatusPending {
|
|
t.Errorf("default Status = %q, want pending", rec.Status)
|
|
}
|
|
|
|
got, err := s.Get(rec.RequestID)
|
|
if err != nil {
|
|
t.Fatalf("Get: %v", err)
|
|
}
|
|
if got.Recipient != rec.Recipient {
|
|
t.Errorf("Recipient = %q, want %q", got.Recipient, rec.Recipient)
|
|
}
|
|
}
|
|
|
|
func TestInMemorySwapStore_GetMissing(t *testing.T) {
|
|
s := newInMemorySwapStore()
|
|
if _, err := s.Get("req_nope"); !errors.Is(err, ErrSwapNotFound) {
|
|
t.Fatalf("missing id: want ErrSwapNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestInMemorySwapStore_Patch(t *testing.T) {
|
|
s := newInMemorySwapStore()
|
|
rec := &BridgeRequestRecord{SourceChain: "A", DestChain: "B", Amount: "1", Recipient: "x"}
|
|
_ = s.Put(rec)
|
|
|
|
out, err := s.Patch(rec.RequestID, func(r *BridgeRequestRecord) {
|
|
r.Status = StatusSigning
|
|
r.SourceTxHash = "0xsrctx"
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Patch: %v", err)
|
|
}
|
|
if out.Status != StatusSigning || out.SourceTxHash != "0xsrctx" {
|
|
t.Errorf("Patch leftovers: %+v", out)
|
|
}
|
|
// Refetch to confirm persistence.
|
|
got, _ := s.Get(rec.RequestID)
|
|
if got.Status != StatusSigning {
|
|
t.Errorf("Status not persisted: %q", got.Status)
|
|
}
|
|
}
|
|
|
|
func TestInMemorySwapStore_ListFilter(t *testing.T) {
|
|
s := newInMemorySwapStore()
|
|
_ = s.Put(&BridgeRequestRecord{SourceChain: "A", Status: StatusPending})
|
|
_ = s.Put(&BridgeRequestRecord{SourceChain: "A", Status: StatusCompleted})
|
|
_ = s.Put(&BridgeRequestRecord{SourceChain: "B", Status: StatusCompleted})
|
|
|
|
all, _ := s.List(SwapListFilter{})
|
|
if len(all) != 3 {
|
|
t.Errorf("unfiltered: got %d, want 3", len(all))
|
|
}
|
|
|
|
done, _ := s.List(SwapListFilter{Status: StatusCompleted})
|
|
if len(done) != 2 {
|
|
t.Errorf("status=completed: got %d, want 2", len(done))
|
|
}
|
|
|
|
aChain, _ := s.List(SwapListFilter{SourceChain: "A"})
|
|
if len(aChain) != 2 {
|
|
t.Errorf("source=A: got %d, want 2", len(aChain))
|
|
}
|
|
|
|
limited, _ := s.List(SwapListFilter{Limit: 1})
|
|
if len(limited) != 1 {
|
|
t.Errorf("limit=1: got %d, want 1", len(limited))
|
|
}
|
|
}
|