mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
Both sides carried work. Ours is the security pass: the stableswap amp=0 and empty-pool halts, the wei-per-swap leak, uint256 truncation in math, three verifiers that returned a verdict without verifying, the cgo/non-cgo consensus split in cggmp21, and the starkfri soundness floor. Theirs is the fhe parameter set, the mldsa crafted-length refusal, and the naming sweep. Resolved per hunk rather than by side. Where both fixed one defect the better fix survives alone, never two guards for one bug.
163 lines
6.9 KiB
Go
163 lines
6.9 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// custody_backing_test.go pins the one question every native funding path asks:
|
|
// how much native did THIS call deliver?
|
|
//
|
|
// 0x9999 holds one native balance backing two pots — settleVault (depositor
|
|
// claims) and custody (value C holds because D owns it: the operator seed plus
|
|
// everything exported to D and not yet imported back). The precompile cannot read
|
|
// msg.value, so it answers by difference: delivered = balance(0x9999) - Σ pots.
|
|
// Subtract fewer pots than exist and the difference reports somebody else's locked
|
|
// funds as value this caller carried, which is a claim over the whole rail.
|
|
|
|
var backingStranger = common.HexToAddress("0x00000000000000000000000000000000000000BA")
|
|
|
|
// backingPots is the non-depositor decomposition the invariant names, so a test can
|
|
// exercise each independently and a new pot cannot be added without appearing here.
|
|
var backingPots = []struct {
|
|
name string
|
|
lock func(stateKV, [32]byte, uint64)
|
|
}{
|
|
{"custody", recordCustodyIn},
|
|
}
|
|
|
|
// TestDepositCannotClaimAnotherPot is the theft. Value held in custody raises the
|
|
// vault's real balance; a deposit that measures itself against settleVault alone
|
|
// reads that value as its own delivery and mints a claim over it, which withdraw
|
|
// then pays out — draining the backing for every claim D has yet to import and
|
|
// every position committed through the rail.
|
|
func TestDepositCannotClaimAnotherPot(t *testing.T) {
|
|
for _, pot := range backingPots {
|
|
t.Run(pot.name, func(t *testing.T) {
|
|
h := newSettleHarness(t)
|
|
db := zzmpDB(h)
|
|
aid := h.inAssetID() // native
|
|
|
|
// Somebody else locks 1_000 native: the vault's real balance rises and
|
|
// this pot — never settleVault — accounts for it.
|
|
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(1_000))
|
|
pot.lock(db, aid, 1_000)
|
|
|
|
// A stranger delivers NOTHING and asks for a claim over the whole pot.
|
|
_, _, err := zzmpRun(h, backingStranger, SelectorDeposit,
|
|
zzmpAssetAmountData(common.Address{}, big.NewInt(1_000)), 5_000_000, false)
|
|
if !errors.Is(err, ErrSettleDepositShort) {
|
|
t.Fatalf("a zero-value deposit against a funded %s: want ErrSettleDepositShort, got %v", pot.name, err)
|
|
}
|
|
if got := loadDepositorClaim(db, backingStranger, aid); got.Sign() != 0 {
|
|
t.Fatalf("a refused deposit minted a claim of %s over the %s", got, pot.name)
|
|
}
|
|
if got := loadSettleVault(db, aid); got.Sign() != 0 {
|
|
t.Fatalf("a refused deposit credited settleVault %s from the %s", got, pot.name)
|
|
}
|
|
// The pot is untouched, so the funds it backs are still payable.
|
|
if got := deliveredNative(db, aid); got.Sign() != 0 {
|
|
t.Fatalf("delivered native = %s with every pot accounted, want 0", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDepositCreditsOnlyWhatThisCallDelivered pins the other side: a real
|
|
// deposit alongside a funded pot still works, and credits exactly what it
|
|
// carried — the refusal above must not be bought by refusing everything.
|
|
func TestDepositCreditsOnlyWhatThisCallDelivered(t *testing.T) {
|
|
h := newSettleHarness(t)
|
|
db := zzmpDB(h)
|
|
aid := h.inAssetID()
|
|
|
|
// 1_000 already held in custody, then this call carries 300.
|
|
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(1_000))
|
|
recordCustodyIn(db, aid, 1_000)
|
|
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(300))
|
|
|
|
if got := deliveredNative(db, aid); got.Int64() != 300 {
|
|
t.Fatalf("delivered native = %s, want the 300 this call carried", got)
|
|
}
|
|
// One wei above what was carried is short.
|
|
if _, _, err := zzmpRun(h, backingStranger, SelectorDeposit,
|
|
zzmpAssetAmountData(common.Address{}, big.NewInt(301)), 5_000_000, false); !errors.Is(err, ErrSettleDepositShort) {
|
|
t.Fatalf("a deposit one wei above the delivery: want ErrSettleDepositShort, got %v", err)
|
|
}
|
|
// Exactly what was carried is accepted.
|
|
if _, _, err := zzmpRun(h, backingStranger, SelectorDeposit,
|
|
zzmpAssetAmountData(common.Address{}, big.NewInt(300)), 5_000_000, false); err != nil {
|
|
t.Fatalf("a deposit of exactly the delivered amount: %v", err)
|
|
}
|
|
if got := loadDepositorClaim(db, backingStranger, aid); got.Int64() != 300 {
|
|
t.Fatalf("claim = %s, want 300", got)
|
|
}
|
|
if got := loadSettleVault(db, aid); got.Int64() != 300 {
|
|
t.Fatalf("settleVault = %s, want 300", got)
|
|
}
|
|
// Every pot is now accounted for, so nothing further is claimable.
|
|
if got := deliveredNative(db, aid); got.Sign() != 0 {
|
|
t.Fatalf("delivered native = %s after the deposit settled, want 0", got)
|
|
}
|
|
}
|
|
|
|
// TestDeliveredNativeIsTheOneAnswer pins that the deposit and the operator seed
|
|
// ask the same question of the same state. Two spellings of "what did this call
|
|
// carry" is how they came to disagree, and the disagreement was the theft.
|
|
func TestDeliveredNativeIsTheOneAnswer(t *testing.T) {
|
|
h := newSettleHarness(t)
|
|
db := zzmpDB(h)
|
|
aid := h.inAssetID()
|
|
|
|
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(900))
|
|
recordCustodyIn(db, aid, 200)
|
|
recordCustodyIn(db, aid, 300)
|
|
storeSettleVault(db, aid, big.NewInt(400))
|
|
|
|
if got := deliveredNative(db, aid); got.Sign() != 0 {
|
|
t.Fatalf("with 200+300+400 accounted against a balance of 900, delivered = %s, want 0", got)
|
|
}
|
|
|
|
// The seed path reaches the same answer through receiveOperatorValue: it must
|
|
// refuse for the same reason, not credit the pots it already counted.
|
|
if _, err := receiveOperatorValue(db, backingStranger, Currency{}, aid, big.NewInt(1),
|
|
func(*big.Int) error { return nil }); !errors.Is(err, ErrSeedUndelivered) {
|
|
t.Fatalf("seed of 1 with nothing delivered: want ErrSeedUndelivered, got %v", err)
|
|
}
|
|
|
|
// A real delivery is seen identically by both.
|
|
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(75))
|
|
if got := deliveredNative(db, aid); got.Int64() != 75 {
|
|
t.Fatalf("delivered = %s, want 75", got)
|
|
}
|
|
if _, err := receiveOperatorValue(db, backingStranger, Currency{}, aid, big.NewInt(75),
|
|
func(*big.Int) error { return nil }); err != nil {
|
|
t.Fatalf("seed of the delivered 75: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestDeliveredNativeNeverReportsMoreThanTheBalance pins the sign: an
|
|
// over-accounted vault (more tracked than held) must report zero delivery, never
|
|
// a negative that a Cmp against a positive amount would read as short-but-close.
|
|
func TestDeliveredNativeNeverReportsMoreThanTheBalance(t *testing.T) {
|
|
h := newSettleHarness(t)
|
|
db := zzmpDB(h)
|
|
aid := h.inAssetID()
|
|
|
|
storeSettleVault(db, aid, big.NewInt(10))
|
|
if got := deliveredNative(db, aid); got.Sign() >= 0 {
|
|
t.Fatalf("tracked above held must report a negative delivery so callers refuse, got %s", got)
|
|
}
|
|
if _, _, err := zzmpRun(h, backingStranger, SelectorDeposit,
|
|
zzmpAssetAmountData(common.Address{}, big.NewInt(1)), 5_000_000, false); !errors.Is(err, ErrSettleDepositShort) {
|
|
t.Fatalf("a deposit against an over-accounted vault: want ErrSettleDepositShort, got %v", err)
|
|
}
|
|
}
|