mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package plugins
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"math/big"
|
|
"testing"
|
|
)
|
|
|
|
// buildLockEvent constructs a fake LockEvent borsh payload for testing parseLockEventFromLogs.
|
|
//
|
|
// Layout after 8-byte discriminator:
|
|
//
|
|
// source_chain: u64 (8) offset 0
|
|
// dest_chain: u64 (8) offset 8
|
|
// nonce: u64 (8) offset 16
|
|
// token: [32]u8 offset 24
|
|
// sender: [32]u8 offset 56
|
|
// recipient: [32]u8 offset 88 (EVM addr in last 20 bytes => offset 100..120)
|
|
// amount: u64 (8) offset 120
|
|
// fee: u64 (8) offset 128
|
|
// timestamp: i64 (8) offset 136
|
|
//
|
|
// Total event body: 144 bytes. With discriminator: 152 bytes.
|
|
func buildLockEvent(srcChain, destChain, nonce, amount, fee uint64, timestamp int64, recipient [20]byte) []byte {
|
|
disc := []byte{0x65, 0xc4, 0x2b, 0x6f, 0x39, 0x48, 0x3e, 0x09}
|
|
buf := make([]byte, 8+144) // discriminator + event body
|
|
copy(buf[:8], disc)
|
|
|
|
d := buf[8:] // event body starts here
|
|
|
|
binary.LittleEndian.PutUint64(d[0:8], srcChain)
|
|
binary.LittleEndian.PutUint64(d[8:16], destChain)
|
|
binary.LittleEndian.PutUint64(d[16:24], nonce)
|
|
|
|
// token (32 bytes at offset 24) -- zero for native SOL
|
|
// sender (32 bytes at offset 56) -- zero for tests
|
|
|
|
// recipient: 32-byte field at offset 88; EVM address zero-padded in last 20 bytes.
|
|
// Matches Solidity: abi.encodePacked(bytes12(0), address).
|
|
copy(d[100:120], recipient[:])
|
|
|
|
binary.LittleEndian.PutUint64(d[120:128], amount)
|
|
binary.LittleEndian.PutUint64(d[128:136], fee)
|
|
binary.LittleEndian.PutUint64(d[136:144], uint64(timestamp))
|
|
|
|
return buf
|
|
}
|
|
|
|
func wrapAsAnchorLog(payload []byte) []string {
|
|
encoded := base64.StdEncoding.EncodeToString(payload)
|
|
return []string{
|
|
"Program log: Instruction: Lock",
|
|
"Program data: " + encoded,
|
|
}
|
|
}
|
|
|
|
func TestParseLockEventFromLogs_BasicVector(t *testing.T) {
|
|
var recipient [20]byte
|
|
for i := range recipient {
|
|
recipient[i] = byte(0xAA + i)
|
|
}
|
|
const (
|
|
nonce uint64 = 42
|
|
amount uint64 = 1_000_000_000 // 1 SOL in lamports
|
|
)
|
|
|
|
payload := buildLockEvent(SolanaChainID, 96369, nonce, amount, 100, 1700000000, recipient)
|
|
logs := wrapAsAnchorLog(payload)
|
|
|
|
ev, ok := parseLockEventFromLogs(logs, 500, "txSig123")
|
|
if !ok {
|
|
t.Fatal("parseLockEventFromLogs returned false; expected a deposit event")
|
|
}
|
|
if ev.SrcChainID != SolanaChainID {
|
|
t.Errorf("SrcChainID = %d, want %d", ev.SrcChainID, SolanaChainID)
|
|
}
|
|
if ev.Nonce != nonce {
|
|
t.Errorf("Nonce = %d, want %d", ev.Nonce, nonce)
|
|
}
|
|
if ev.Recipient != recipient {
|
|
t.Errorf("Recipient = %x, want %x", ev.Recipient, recipient)
|
|
}
|
|
if ev.Amount.Uint64() != amount {
|
|
t.Errorf("Amount = %s, want %d", ev.Amount, amount)
|
|
}
|
|
if ev.BlockHeight != 500 {
|
|
t.Errorf("BlockHeight = %d, want 500", ev.BlockHeight)
|
|
}
|
|
if ev.TxID != "txSig123" {
|
|
t.Errorf("TxID = %q, want txSig123", ev.TxID)
|
|
}
|
|
}
|
|
|
|
// Regression: amount was previously read from the wrong offset, confusing recipient
|
|
// bytes with amount bytes. Verify two different amounts produce correct results.
|
|
func TestParseLockEventFromLogs_AmountOffset(t *testing.T) {
|
|
var recip [20]byte
|
|
recip[19] = 0x01 // minimal address
|
|
|
|
tests := []struct {
|
|
name string
|
|
amount uint64
|
|
}{
|
|
{"1_SOL", 1_000_000_000},
|
|
{"0.001_SOL", 1_000_000},
|
|
{"max_u32", 4_294_967_295},
|
|
{"large", 9_999_999_999_999},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
payload := buildLockEvent(SolanaChainID, 96369, 1, tt.amount, 0, 1700000000, recip)
|
|
logs := wrapAsAnchorLog(payload)
|
|
|
|
ev, ok := parseLockEventFromLogs(logs, 100, "tx")
|
|
if !ok {
|
|
t.Fatalf("%s: parseLockEventFromLogs returned false", tt.name)
|
|
}
|
|
if ev.Amount.Uint64() != tt.amount {
|
|
t.Errorf("%s: Amount = %d, want %d", tt.name, ev.Amount.Uint64(), tt.amount)
|
|
}
|
|
if ev.Recipient != recip {
|
|
t.Errorf("%s: Recipient = %x, want %x", tt.name, ev.Recipient, recip)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify the amount field is at d[120:128], not confused with any other field.
|
|
func TestParseLockEventFromLogs_AmountNotRecipient(t *testing.T) {
|
|
// Set a distinctive recipient where the "last 8 bytes" differ from the amount.
|
|
var recip [20]byte
|
|
recip[0] = 0xFF
|
|
recip[19] = 0xEE
|
|
|
|
const amount uint64 = 777_777_777
|
|
|
|
payload := buildLockEvent(SolanaChainID, 96369, 10, amount, 50, 1700000000, recip)
|
|
logs := wrapAsAnchorLog(payload)
|
|
|
|
ev, ok := parseLockEventFromLogs(logs, 200, "txABC")
|
|
if !ok {
|
|
t.Fatal("parseLockEventFromLogs returned false")
|
|
}
|
|
|
|
// Amount must not be equal to any fragment of the recipient field.
|
|
wantAmount := big.NewInt(int64(amount))
|
|
if ev.Amount.Cmp(wantAmount) != 0 {
|
|
t.Errorf("Amount = %s, want %s (may be reading recipient bytes instead)", ev.Amount, wantAmount)
|
|
}
|
|
}
|
|
|
|
func TestParseLockEventFromLogs_WrongDiscriminator(t *testing.T) {
|
|
payload := make([]byte, 152)
|
|
// Wrong discriminator.
|
|
payload[0] = 0x00
|
|
logs := wrapAsAnchorLog(payload)
|
|
|
|
_, ok := parseLockEventFromLogs(logs, 100, "tx")
|
|
if ok {
|
|
t.Error("expected false for wrong discriminator")
|
|
}
|
|
}
|
|
|
|
func TestParseLockEventFromLogs_ShortPayload(t *testing.T) {
|
|
disc := []byte{0x65, 0xc4, 0x2b, 0x6f, 0x39, 0x48, 0x3e, 0x09}
|
|
payload := make([]byte, 8+10) // too short
|
|
copy(payload[:8], disc)
|
|
logs := wrapAsAnchorLog(payload)
|
|
|
|
_, ok := parseLockEventFromLogs(logs, 100, "tx")
|
|
if ok {
|
|
t.Error("expected false for short payload")
|
|
}
|
|
}
|
|
|
|
func TestParseLockEventFromLogs_NoMatchingLog(t *testing.T) {
|
|
logs := []string{
|
|
"Program log: something else",
|
|
"not a program data line",
|
|
}
|
|
_, ok := parseLockEventFromLogs(logs, 100, "tx")
|
|
if ok {
|
|
t.Error("expected false when no matching log line")
|
|
}
|
|
}
|
|
|
|
func TestParseLockEventFromLogs_EmptyLogs(t *testing.T) {
|
|
_, ok := parseLockEventFromLogs(nil, 100, "tx")
|
|
if ok {
|
|
t.Error("expected false for nil logs")
|
|
}
|
|
}
|
|
|
|
func TestLe64(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in [8]byte
|
|
want uint64
|
|
}{
|
|
{"zero", [8]byte{}, 0},
|
|
{"one", [8]byte{1, 0, 0, 0, 0, 0, 0, 0}, 1},
|
|
{"256", [8]byte{0, 1, 0, 0, 0, 0, 0, 0}, 256},
|
|
{"1e9", func() [8]byte {
|
|
var b [8]byte
|
|
binary.LittleEndian.PutUint64(b[:], 1_000_000_000)
|
|
return b
|
|
}(), 1_000_000_000},
|
|
{"max", [8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, ^uint64(0)},
|
|
}
|
|
for _, tt := range tests {
|
|
got := le64(tt.in[:])
|
|
if got != tt.want {
|
|
t.Errorf("%s: le64(%x) = %d, want %d", tt.name, tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|