mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
refactor(bridge): remove dead custodial lock-mint gateway + MPC signer
The bridge package carried a full custodial lock-mint cross-chain bridge (BridgeGateway: liquidity pools, daily limits, a 2/3 signer set) and an in-memory MPC attestation layer (BridgeSigner) — neither wired to any precompile address or Run dispatch, both guarding fund movement with STUB verifiers (verifySignature / VerifyThresholdSignature returned len(signature) > 0, accepting any non-empty bytes). Cross-chain value movement is now the sole responsibility of the HTLC atomic-swap precompile LP-90A0 (swap/), where every payout is backed by a real transferFrom-observed lock and released only on a SHA-256 preimage or timeout — non-custody is structural, not policy. Keeping a second, dormant custodial path violated 'exactly one way' and left a mint surface that could later be wired behind a fake verifier. Removes gateway.go, signer.go, types.go and their tests; bridge/ is now a pure chain registry (registrar precompile, registry rows — no fund movement). No reachable code path changes: the deleted machinery was depended on by nothing outside its own files (verified by build + grep). go vet + go test -race ./bridge/... green; registry LP-90A0 intact.
This commit is contained in:
@@ -1,557 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// BridgeGateway is the main bridge interface for cross-chain transfers
|
||||
// This precompile at 0x0440 handles bridge initiation and completion
|
||||
type BridgeGateway struct {
|
||||
// State
|
||||
Requests map[[32]byte]*BridgeRequest
|
||||
Nonces map[common.Address]uint64 // Per-address nonces
|
||||
SupportedTokens map[common.Address]*BridgedToken
|
||||
SupportedChains map[uint32]bool
|
||||
|
||||
// Liquidity pools
|
||||
Pools map[uint32]map[common.Address]*LiquidityPool // ChainID -> Token -> Pool
|
||||
|
||||
// Signer set (interface to B-Chain)
|
||||
SignerSet *SignerSet
|
||||
|
||||
// Configuration
|
||||
Config *BridgeFeeConfig
|
||||
Enabled bool
|
||||
Paused bool
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBridgeGateway creates a new bridge gateway
|
||||
func NewBridgeGateway() *BridgeGateway {
|
||||
gw := &BridgeGateway{
|
||||
Requests: make(map[[32]byte]*BridgeRequest),
|
||||
Nonces: make(map[common.Address]uint64),
|
||||
SupportedTokens: make(map[common.Address]*BridgedToken),
|
||||
SupportedChains: make(map[uint32]bool),
|
||||
Pools: make(map[uint32]map[common.Address]*LiquidityPool),
|
||||
SignerSet: &SignerSet{
|
||||
Signers: make([]*SignerInfo, 0),
|
||||
Waitlist: make([][20]byte, 0),
|
||||
Threshold: 67, // 2/3 default
|
||||
},
|
||||
Config: &BridgeFeeConfig{
|
||||
BaseFee: big.NewInt(1e15), // 0.001 token
|
||||
PercentFee: 30, // 0.3%
|
||||
MinFee: big.NewInt(1e15), // 0.001 token
|
||||
MaxFee: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100)), // 100 tokens
|
||||
LiquidityFee: 20, // 0.2% to LPs
|
||||
ProtocolFee: 10, // 0.1% protocol
|
||||
},
|
||||
Enabled: true,
|
||||
Paused: false,
|
||||
}
|
||||
|
||||
// Initialize supported chains
|
||||
gw.initSupportedChains()
|
||||
|
||||
return gw
|
||||
}
|
||||
|
||||
// NewBridgeGatewayWithRegistry constructs a gateway whose supported-chain set
|
||||
// is sourced from r. Used in tests and on networks that want to pin the set to
|
||||
// a curated list rather than the hard-coded ecosystem defaults.
|
||||
//
|
||||
// The gateway's SupportedChains map mirrors r.All() at construction time.
|
||||
// Live updates to r are not reflected; callers that need dynamic membership
|
||||
// should use a state-backed registry behind the precompile entry point.
|
||||
func NewBridgeGatewayWithRegistry(r Registry) *BridgeGateway {
|
||||
gw := &BridgeGateway{
|
||||
Requests: make(map[[32]byte]*BridgeRequest),
|
||||
Nonces: make(map[common.Address]uint64),
|
||||
SupportedTokens: make(map[common.Address]*BridgedToken),
|
||||
SupportedChains: make(map[uint32]bool),
|
||||
Pools: make(map[uint32]map[common.Address]*LiquidityPool),
|
||||
SignerSet: &SignerSet{
|
||||
Signers: make([]*SignerInfo, 0),
|
||||
Waitlist: make([][20]byte, 0),
|
||||
Threshold: 67,
|
||||
},
|
||||
Config: &BridgeFeeConfig{
|
||||
BaseFee: big.NewInt(1e15),
|
||||
PercentFee: 30,
|
||||
MinFee: big.NewInt(1e15),
|
||||
MaxFee: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100)),
|
||||
LiquidityFee: 20,
|
||||
ProtocolFee: 10,
|
||||
},
|
||||
Enabled: true,
|
||||
Paused: false,
|
||||
}
|
||||
for _, c := range r.All() {
|
||||
gw.SupportedChains[uint32(c.ID)] = true
|
||||
}
|
||||
return gw
|
||||
}
|
||||
|
||||
// Supports reports whether id is in the gateway's supported-chain set.
|
||||
func (gw *BridgeGateway) Supports(id uint32) bool {
|
||||
gw.mu.RLock()
|
||||
defer gw.mu.RUnlock()
|
||||
return gw.SupportedChains[id]
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) initSupportedChains() {
|
||||
// Lux ecosystem
|
||||
gw.SupportedChains[ChainLux] = true
|
||||
gw.SupportedChains[ChainLuxTest] = true
|
||||
gw.SupportedChains[ChainHanzo] = true
|
||||
gw.SupportedChains[ChainHanzoTest] = true
|
||||
gw.SupportedChains[ChainZoo] = true
|
||||
gw.SupportedChains[ChainZooTest] = true
|
||||
gw.SupportedChains[ChainSPC] = true
|
||||
gw.SupportedChains[ChainSPCTest] = true
|
||||
|
||||
// External chains
|
||||
gw.SupportedChains[ChainEthereum] = true
|
||||
gw.SupportedChains[ChainArbitrum] = true
|
||||
gw.SupportedChains[ChainOptimism] = true
|
||||
gw.SupportedChains[ChainBase] = true
|
||||
gw.SupportedChains[ChainPolygon] = true
|
||||
gw.SupportedChains[ChainBSC] = true
|
||||
gw.SupportedChains[ChainAvalanche] = true
|
||||
}
|
||||
|
||||
// InitiateBridge starts a cross-chain transfer
|
||||
func (gw *BridgeGateway) InitiateBridge(
|
||||
sender common.Address,
|
||||
recipient common.Address,
|
||||
token common.Address,
|
||||
amount *big.Int,
|
||||
sourceChain uint32,
|
||||
destChain uint32,
|
||||
deadline uint64,
|
||||
data []byte,
|
||||
) (*BridgeRequest, error) {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
// Validations
|
||||
if !gw.Enabled || gw.Paused {
|
||||
return nil, ErrBridgeDisabled
|
||||
}
|
||||
|
||||
if !gw.SupportedChains[destChain] {
|
||||
return nil, ErrChainNotSupported
|
||||
}
|
||||
|
||||
// Check token is supported
|
||||
tokenInfo := gw.SupportedTokens[token]
|
||||
if tokenInfo == nil || !tokenInfo.Enabled {
|
||||
return nil, ErrTokenNotSupported
|
||||
}
|
||||
|
||||
// Check amount limits
|
||||
if amount.Cmp(tokenInfo.MinBridge) < 0 {
|
||||
return nil, ErrAmountTooLow
|
||||
}
|
||||
if tokenInfo.MaxBridge.Sign() > 0 && amount.Cmp(tokenInfo.MaxBridge) > 0 {
|
||||
return nil, ErrAmountTooHigh
|
||||
}
|
||||
|
||||
// Check daily limit
|
||||
gw.resetDailyLimitIfNeeded(tokenInfo)
|
||||
newTotal := new(big.Int).Add(tokenInfo.BridgedToday, amount)
|
||||
if tokenInfo.DailyLimit.Sign() > 0 && newTotal.Cmp(tokenInfo.DailyLimit) > 0 {
|
||||
return nil, ErrDailyLimitExceeded
|
||||
}
|
||||
|
||||
// Check liquidity on destination chain
|
||||
pool := gw.getPool(destChain, token)
|
||||
if pool == nil || pool.Available.Cmp(amount) < 0 {
|
||||
return nil, ErrInsufficientLiquidity
|
||||
}
|
||||
|
||||
// Generate request ID
|
||||
nonce := gw.Nonces[sender]
|
||||
gw.Nonces[sender] = nonce + 1
|
||||
|
||||
requestID := gw.generateRequestID(sender, recipient, token, amount, sourceChain, destChain, nonce)
|
||||
|
||||
// Calculate fee
|
||||
fee := gw.calculateFee(amount)
|
||||
|
||||
// Create request
|
||||
request := &BridgeRequest{
|
||||
ID: requestID,
|
||||
Sender: sender,
|
||||
Recipient: recipient,
|
||||
Token: token,
|
||||
Amount: new(big.Int).Sub(amount, fee), // Amount after fee
|
||||
SourceChain: sourceChain,
|
||||
DestChain: destChain,
|
||||
Nonce: nonce,
|
||||
Deadline: deadline,
|
||||
Data: data,
|
||||
Status: StatusPending,
|
||||
Signatures: make([][]byte, 0),
|
||||
CreatedAt: uint64(time.Now().Unix()),
|
||||
}
|
||||
|
||||
// Update state
|
||||
gw.Requests[requestID] = request
|
||||
tokenInfo.BridgedToday.Add(tokenInfo.BridgedToday, amount)
|
||||
|
||||
// Reserve liquidity on destination
|
||||
pool.Available.Sub(pool.Available, request.Amount)
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// CompleteBridge completes a bridge on the destination chain
|
||||
func (gw *BridgeGateway) CompleteBridge(
|
||||
requestID [32]byte,
|
||||
signatures [][]byte,
|
||||
) error {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
request := gw.Requests[requestID]
|
||||
if request == nil {
|
||||
return ErrRequestNotFound
|
||||
}
|
||||
|
||||
if request.Status == StatusCompleted {
|
||||
return ErrRequestAlreadyDone
|
||||
}
|
||||
|
||||
if request.Deadline > 0 && uint64(time.Now().Unix()) > request.Deadline {
|
||||
request.Status = StatusExpired
|
||||
return ErrRequestExpired
|
||||
}
|
||||
|
||||
// Verify signatures meet threshold
|
||||
if uint32(len(signatures)) < gw.getThreshold() {
|
||||
return ErrSignatureThreshold
|
||||
}
|
||||
|
||||
// Verify each signature
|
||||
message := gw.encodeMessage(request)
|
||||
for _, sig := range signatures {
|
||||
if !gw.verifySignature(message, sig) {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
}
|
||||
|
||||
// Mark completed
|
||||
request.Status = StatusCompleted
|
||||
request.Signatures = signatures
|
||||
request.CompletedAt = uint64(time.Now().Unix())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequest returns a bridge request by ID
|
||||
func (gw *BridgeGateway) GetRequest(requestID [32]byte) (*BridgeRequest, error) {
|
||||
gw.mu.RLock()
|
||||
defer gw.mu.RUnlock()
|
||||
|
||||
request := gw.Requests[requestID]
|
||||
if request == nil {
|
||||
return nil, ErrRequestNotFound
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// RefundExpired refunds an expired bridge request
|
||||
func (gw *BridgeGateway) RefundExpired(requestID [32]byte) error {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
request := gw.Requests[requestID]
|
||||
if request == nil {
|
||||
return ErrRequestNotFound
|
||||
}
|
||||
|
||||
if request.Status == StatusCompleted || request.Status == StatusRefunded {
|
||||
return ErrRequestAlreadyDone
|
||||
}
|
||||
|
||||
if request.Deadline > 0 && uint64(time.Now().Unix()) <= request.Deadline {
|
||||
return errors.New("request not yet expired")
|
||||
}
|
||||
|
||||
// Return liquidity to pool
|
||||
pool := gw.getPool(request.DestChain, request.Token)
|
||||
if pool != nil {
|
||||
pool.Available.Add(pool.Available, request.Amount)
|
||||
}
|
||||
|
||||
request.Status = StatusRefunded
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLiquidity adds liquidity to a bridge pool
|
||||
func (gw *BridgeGateway) AddLiquidity(
|
||||
provider common.Address,
|
||||
token common.Address,
|
||||
chainID uint32,
|
||||
amount *big.Int,
|
||||
) (*LPPosition, error) {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
if !gw.SupportedChains[chainID] {
|
||||
return nil, ErrChainNotSupported
|
||||
}
|
||||
|
||||
pool := gw.getOrCreatePool(chainID, token)
|
||||
|
||||
// Calculate share ratio
|
||||
var shareRatio *big.Int
|
||||
if pool.TotalLiq.Sign() == 0 {
|
||||
shareRatio = new(big.Int).Mul(amount, big.NewInt(1e18))
|
||||
} else {
|
||||
shareRatio = new(big.Int).Mul(amount, big.NewInt(1e18))
|
||||
shareRatio.Div(shareRatio, pool.TotalLiq)
|
||||
}
|
||||
|
||||
// Update or create position
|
||||
position := pool.Providers[provider]
|
||||
if position == nil {
|
||||
position = &LPPosition{
|
||||
Provider: provider,
|
||||
Amount: big.NewInt(0),
|
||||
ShareRatio: big.NewInt(0),
|
||||
DepositTime: uint64(time.Now().Unix()),
|
||||
PendingFees: big.NewInt(0),
|
||||
}
|
||||
pool.Providers[provider] = position
|
||||
}
|
||||
|
||||
position.Amount.Add(position.Amount, amount)
|
||||
position.ShareRatio.Add(position.ShareRatio, shareRatio)
|
||||
|
||||
// Update pool totals
|
||||
pool.TotalLiq.Add(pool.TotalLiq, amount)
|
||||
pool.Available.Add(pool.Available, amount)
|
||||
|
||||
return position, nil
|
||||
}
|
||||
|
||||
// RemoveLiquidity removes liquidity from a bridge pool
|
||||
func (gw *BridgeGateway) RemoveLiquidity(
|
||||
provider common.Address,
|
||||
token common.Address,
|
||||
chainID uint32,
|
||||
amount *big.Int,
|
||||
) (*big.Int, error) {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
pool := gw.getPool(chainID, token)
|
||||
if pool == nil {
|
||||
return nil, ErrInsufficientLiquidity
|
||||
}
|
||||
|
||||
position := pool.Providers[provider]
|
||||
if position == nil || position.Amount.Cmp(amount) < 0 {
|
||||
return nil, ErrInsufficientLiquidity
|
||||
}
|
||||
|
||||
if pool.Available.Cmp(amount) < 0 {
|
||||
return nil, errors.New("liquidity currently in use")
|
||||
}
|
||||
|
||||
// Calculate fees earned
|
||||
fees := gw.calculateLPFees(pool, position)
|
||||
|
||||
// Update position
|
||||
position.Amount.Sub(position.Amount, amount)
|
||||
if position.Amount.Sign() == 0 {
|
||||
delete(pool.Providers, provider)
|
||||
}
|
||||
|
||||
// Update pool totals
|
||||
pool.TotalLiq.Sub(pool.TotalLiq, amount)
|
||||
pool.Available.Sub(pool.Available, amount)
|
||||
|
||||
// Return amount + fees
|
||||
total := new(big.Int).Add(amount, fees)
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// RegisterToken registers a new token for bridging
|
||||
func (gw *BridgeGateway) RegisterToken(
|
||||
localAddress common.Address,
|
||||
decimals uint8,
|
||||
symbol string,
|
||||
name string,
|
||||
minBridge *big.Int,
|
||||
maxBridge *big.Int,
|
||||
dailyLimit *big.Int,
|
||||
) error {
|
||||
gw.mu.Lock()
|
||||
defer gw.mu.Unlock()
|
||||
|
||||
if gw.SupportedTokens[localAddress] != nil {
|
||||
return errors.New("token already registered")
|
||||
}
|
||||
|
||||
gw.SupportedTokens[localAddress] = &BridgedToken{
|
||||
LocalAddress: localAddress,
|
||||
RemoteAddress: make(map[uint32]common.Address),
|
||||
Decimals: decimals,
|
||||
Symbol: symbol,
|
||||
Name: name,
|
||||
MinBridge: minBridge,
|
||||
MaxBridge: maxBridge,
|
||||
DailyLimit: dailyLimit,
|
||||
BridgedToday: big.NewInt(0),
|
||||
LastReset: uint64(time.Now().Unix()),
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (gw *BridgeGateway) generateRequestID(
|
||||
sender, recipient common.Address,
|
||||
token common.Address,
|
||||
amount *big.Int,
|
||||
sourceChain, destChain uint32,
|
||||
nonce uint64,
|
||||
) [32]byte {
|
||||
data := append(sender.Bytes(), recipient.Bytes()...)
|
||||
data = append(data, token.Bytes()...)
|
||||
data = append(data, amount.Bytes()...)
|
||||
data = append(data, big.NewInt(int64(sourceChain)).Bytes()...)
|
||||
data = append(data, big.NewInt(int64(destChain)).Bytes()...)
|
||||
data = append(data, big.NewInt(int64(nonce)).Bytes()...)
|
||||
return sha256.Sum256(data)
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) calculateFee(amount *big.Int) *big.Int {
|
||||
// Percent fee
|
||||
fee := new(big.Int).Mul(amount, big.NewInt(int64(gw.Config.PercentFee)))
|
||||
fee.Div(fee, big.NewInt(10000))
|
||||
|
||||
// Add base fee
|
||||
fee.Add(fee, gw.Config.BaseFee)
|
||||
|
||||
// Apply min/max
|
||||
if fee.Cmp(gw.Config.MinFee) < 0 {
|
||||
fee.Set(gw.Config.MinFee)
|
||||
}
|
||||
if gw.Config.MaxFee.Sign() > 0 && fee.Cmp(gw.Config.MaxFee) > 0 {
|
||||
fee.Set(gw.Config.MaxFee)
|
||||
}
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) getPool(chainID uint32, token common.Address) *LiquidityPool {
|
||||
chainPools := gw.Pools[chainID]
|
||||
if chainPools == nil {
|
||||
return nil
|
||||
}
|
||||
return chainPools[token]
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) getOrCreatePool(chainID uint32, token common.Address) *LiquidityPool {
|
||||
if gw.Pools[chainID] == nil {
|
||||
gw.Pools[chainID] = make(map[common.Address]*LiquidityPool)
|
||||
}
|
||||
|
||||
pool := gw.Pools[chainID][token]
|
||||
if pool == nil {
|
||||
pool = &LiquidityPool{
|
||||
Token: token,
|
||||
ChainID: chainID,
|
||||
TotalLiq: big.NewInt(0),
|
||||
Available: big.NewInt(0),
|
||||
Providers: make(map[common.Address]*LPPosition),
|
||||
FeeRate: gw.Config.LiquidityFee,
|
||||
TotalFees: big.NewInt(0),
|
||||
}
|
||||
gw.Pools[chainID][token] = pool
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) resetDailyLimitIfNeeded(token *BridgedToken) {
|
||||
now := uint64(time.Now().Unix())
|
||||
daySeconds := uint64(86400)
|
||||
if now-token.LastReset >= daySeconds {
|
||||
token.BridgedToday = big.NewInt(0)
|
||||
token.LastReset = now
|
||||
}
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) getThreshold() uint32 {
|
||||
if gw.SignerSet == nil || len(gw.SignerSet.Signers) == 0 {
|
||||
return 1
|
||||
}
|
||||
// 2/3 + 1 of active signers
|
||||
return (uint32(len(gw.SignerSet.Signers)) * 2 / 3) + 1
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) encodeMessage(request *BridgeRequest) []byte {
|
||||
msg := &BridgeMessage{
|
||||
Version: 1,
|
||||
MessageType: MsgTypeTransfer,
|
||||
SourceChain: request.SourceChain,
|
||||
DestChain: request.DestChain,
|
||||
Nonce: request.Nonce,
|
||||
Sender: request.Sender,
|
||||
Recipient: request.Recipient,
|
||||
Token: request.Token,
|
||||
Amount: request.Amount,
|
||||
Data: request.Data,
|
||||
Timestamp: request.CreatedAt,
|
||||
}
|
||||
|
||||
// Encode message (simplified - would use proper encoding)
|
||||
data := []byte{msg.Version, msg.MessageType}
|
||||
data = append(data, big.NewInt(int64(msg.SourceChain)).Bytes()...)
|
||||
data = append(data, big.NewInt(int64(msg.DestChain)).Bytes()...)
|
||||
data = append(data, big.NewInt(int64(msg.Nonce)).Bytes()...)
|
||||
data = append(data, msg.Sender.Bytes()...)
|
||||
data = append(data, msg.Recipient.Bytes()...)
|
||||
data = append(data, msg.Token.Bytes()...)
|
||||
data = append(data, msg.Amount.Bytes()...)
|
||||
data = append(data, msg.Data...)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) verifySignature(message []byte, signature []byte) bool {
|
||||
// In production, verify against signer set public keys
|
||||
// This would call into B-Chain's MPC verification
|
||||
return len(signature) > 0
|
||||
}
|
||||
|
||||
func (gw *BridgeGateway) calculateLPFees(pool *LiquidityPool, position *LPPosition) *big.Int {
|
||||
if pool.TotalFees.Sign() == 0 || position.ShareRatio.Sign() == 0 {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
|
||||
// fees = totalFees * shareRatio / 1e18
|
||||
fees := new(big.Int).Mul(pool.TotalFees, position.ShareRatio)
|
||||
fees.Div(fees, big.NewInt(1e18))
|
||||
|
||||
return fees
|
||||
}
|
||||
@@ -1,655 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// Helper functions for large big.Int values (avoid overflow in big.NewInt)
|
||||
func bigExp(base, exp int64) *big.Int {
|
||||
result := big.NewInt(1)
|
||||
b := big.NewInt(base)
|
||||
for range exp {
|
||||
result.Mul(result, b)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// e19 returns 10^19
|
||||
func e19() *big.Int { return bigExp(10, 19) }
|
||||
|
||||
// e20 returns 10^20
|
||||
func e20() *big.Int { return bigExp(10, 20) }
|
||||
|
||||
// e21 returns 10^21
|
||||
func e21() *big.Int { return bigExp(10, 21) }
|
||||
|
||||
// e24 returns 10^24
|
||||
func e24() *big.Int { return bigExp(10, 24) }
|
||||
|
||||
// e25 returns 10^25
|
||||
func e25() *big.Int { return bigExp(10, 25) }
|
||||
|
||||
// e22 returns 10^22
|
||||
func e22() *big.Int { return bigExp(10, 22) }
|
||||
|
||||
// e30 returns 10^30
|
||||
func e30() *big.Int { return bigExp(10, 30) }
|
||||
|
||||
// fiveE19 returns 5 * 10^19
|
||||
func fiveE19() *big.Int { return new(big.Int).Mul(big.NewInt(5), e19()) }
|
||||
|
||||
// TestNewBridgeGateway tests gateway creation
|
||||
func TestNewBridgeGateway(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
if gw == nil {
|
||||
t.Fatal("Expected non-nil BridgeGateway")
|
||||
}
|
||||
|
||||
if gw.Requests == nil {
|
||||
t.Error("Expected Requests map to be initialized")
|
||||
}
|
||||
if gw.Nonces == nil {
|
||||
t.Error("Expected Nonces map to be initialized")
|
||||
}
|
||||
if gw.SupportedTokens == nil {
|
||||
t.Error("Expected SupportedTokens map to be initialized")
|
||||
}
|
||||
if gw.SupportedChains == nil {
|
||||
t.Error("Expected SupportedChains map to be initialized")
|
||||
}
|
||||
if gw.Pools == nil {
|
||||
t.Error("Expected Pools map to be initialized")
|
||||
}
|
||||
if gw.SignerSet == nil {
|
||||
t.Error("Expected SignerSet to be initialized")
|
||||
}
|
||||
if gw.Config == nil {
|
||||
t.Error("Expected Config to be initialized")
|
||||
}
|
||||
if !gw.Enabled {
|
||||
t.Error("Expected gateway to be enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSupportedChains tests chain support initialization
|
||||
func TestSupportedChains(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
expectedChains := []uint32{
|
||||
ChainLux, ChainLuxTest,
|
||||
ChainHanzo, ChainHanzoTest,
|
||||
ChainZoo, ChainZooTest,
|
||||
ChainSPC, ChainSPCTest,
|
||||
ChainEthereum, ChainArbitrum, ChainOptimism,
|
||||
ChainBase, ChainPolygon, ChainBSC, ChainAvalanche,
|
||||
}
|
||||
|
||||
for _, chainID := range expectedChains {
|
||||
if !gw.SupportedChains[chainID] {
|
||||
t.Errorf("Expected chain %d to be supported", chainID)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify unsupported chain
|
||||
if gw.SupportedChains[99999] {
|
||||
t.Error("Chain 99999 should not be supported")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterToken tests token registration
|
||||
func TestRegisterToken(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
err := gw.RegisterToken(
|
||||
token,
|
||||
18,
|
||||
"TEST",
|
||||
"Test Token",
|
||||
big.NewInt(1e17), // min bridge
|
||||
new(big.Int).Mul(big.NewInt(1e12), big.NewInt(1e12)), // max bridge (1e24)
|
||||
new(big.Int).Mul(big.NewInt(1e13), big.NewInt(1e12)), // daily limit (1e25)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("RegisterToken failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify token was registered
|
||||
tokenInfo := gw.SupportedTokens[token]
|
||||
if tokenInfo == nil {
|
||||
t.Fatal("Token not stored")
|
||||
}
|
||||
if tokenInfo.Symbol != "TEST" {
|
||||
t.Errorf("Expected symbol TEST, got %s", tokenInfo.Symbol)
|
||||
}
|
||||
if tokenInfo.Decimals != 18 {
|
||||
t.Errorf("Expected decimals 18, got %d", tokenInfo.Decimals)
|
||||
}
|
||||
if !tokenInfo.Enabled {
|
||||
t.Error("Expected token to be enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterTokenDuplicate tests duplicate token registration
|
||||
func TestRegisterTokenDuplicate(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
// First registration
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1), e24(), e25())
|
||||
|
||||
// Duplicate registration
|
||||
err := gw.RegisterToken(token, 18, "TEST2", "Test2", big.NewInt(1), e24(), e25())
|
||||
if err == nil {
|
||||
t.Error("Expected error for duplicate token registration")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridge tests bridge initiation
|
||||
func TestInitiateBridge(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// Setup token
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
|
||||
// Add liquidity on destination chain
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
|
||||
|
||||
// Initiate bridge
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
recipient := common.HexToAddress("0xABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD")
|
||||
amount := big.NewInt(1e18)
|
||||
|
||||
request, err := gw.InitiateBridge(
|
||||
sender,
|
||||
recipient,
|
||||
token,
|
||||
amount,
|
||||
ChainLux,
|
||||
ChainEthereum,
|
||||
uint64(time.Now().Add(time.Hour).Unix()),
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("InitiateBridge failed: %v", err)
|
||||
}
|
||||
|
||||
if request == nil {
|
||||
t.Fatal("Expected non-nil request")
|
||||
}
|
||||
if request.ID == [32]byte{} {
|
||||
t.Error("Expected non-zero request ID")
|
||||
}
|
||||
if request.Sender != sender {
|
||||
t.Error("Sender mismatch")
|
||||
}
|
||||
if request.Recipient != recipient {
|
||||
t.Error("Recipient mismatch")
|
||||
}
|
||||
if request.Status != StatusPending {
|
||||
t.Errorf("Expected pending status, got %v", request.Status)
|
||||
}
|
||||
|
||||
// Verify nonce incremented
|
||||
if gw.Nonces[sender] != 1 {
|
||||
t.Errorf("Expected nonce 1, got %d", gw.Nonces[sender])
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeDisabled tests bridging when gateway is disabled
|
||||
func TestInitiateBridgeDisabled(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
gw.Enabled = false
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), 1, 2, 0, nil)
|
||||
if err != ErrBridgeDisabled {
|
||||
t.Errorf("Expected ErrBridgeDisabled, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgePaused tests bridging when gateway is paused
|
||||
func TestInitiateBridgePaused(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
gw.Paused = true
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), 1, 2, 0, nil)
|
||||
if err != ErrBridgeDisabled {
|
||||
t.Errorf("Expected ErrBridgeDisabled, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeChainNotSupported tests unsupported destination chain
|
||||
func TestInitiateBridgeChainNotSupported(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, common.Address{}, big.NewInt(1), ChainLux, 99999, 0, nil)
|
||||
if err != ErrChainNotSupported {
|
||||
t.Errorf("Expected ErrChainNotSupported, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeTokenNotSupported tests unsupported token
|
||||
func TestInitiateBridgeTokenNotSupported(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
unsupportedToken := common.HexToAddress("0xDEADDEADDEADDEADDEADDEADDEADDEADDEADDEAD")
|
||||
|
||||
_, err := gw.InitiateBridge(sender, sender, unsupportedToken, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
|
||||
if err != ErrTokenNotSupported {
|
||||
t.Errorf("Expected ErrTokenNotSupported, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeAmountTooLow tests minimum amount validation
|
||||
func TestInitiateBridgeAmountTooLow(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, token, big.NewInt(1e16), ChainLux, ChainEthereum, 0, nil) // Below min
|
||||
if err != ErrAmountTooLow {
|
||||
t.Errorf("Expected ErrAmountTooLow, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeAmountTooHigh tests maximum amount validation
|
||||
func TestInitiateBridgeAmountTooHigh(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e20(), e25()) // Max 100 tokens
|
||||
|
||||
// Add liquidity
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, token, e21(), ChainLux, ChainEthereum, 0, nil) // Above max
|
||||
if err != ErrAmountTooHigh {
|
||||
t.Errorf("Expected ErrAmountTooHigh, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeInsufficientLiquidity tests liquidity check
|
||||
func TestInitiateBridgeInsufficientLiquidity(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
|
||||
// No liquidity added
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
|
||||
if err != ErrInsufficientLiquidity {
|
||||
t.Errorf("Expected ErrInsufficientLiquidity, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitiateBridgeDailyLimitExceeded tests daily limit
|
||||
func TestInitiateBridgeDailyLimitExceeded(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e19()) // Daily limit 10 tokens
|
||||
|
||||
// Add liquidity
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
_, err := gw.InitiateBridge(sender, sender, token, e20(), ChainLux, ChainEthereum, 0, nil) // Exceeds daily limit
|
||||
if err != ErrDailyLimitExceeded {
|
||||
t.Errorf("Expected ErrDailyLimitExceeded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompleteBridge tests bridge completion
|
||||
func TestCompleteBridge(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// Setup
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
|
||||
|
||||
// Initiate
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
|
||||
|
||||
// Complete with signatures
|
||||
signatures := [][]byte{[]byte("sig1")}
|
||||
err := gw.CompleteBridge(request.ID, signatures)
|
||||
if err != nil {
|
||||
t.Fatalf("CompleteBridge failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify status
|
||||
completedRequest, _ := gw.GetRequest(request.ID)
|
||||
if completedRequest.Status != StatusCompleted {
|
||||
t.Errorf("Expected completed status, got %v", completedRequest.Status)
|
||||
}
|
||||
if completedRequest.CompletedAt == 0 {
|
||||
t.Error("Expected non-zero CompletedAt")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompleteBridgeNotFound tests error for non-existent request
|
||||
func TestCompleteBridgeNotFound(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
nonExistent := [32]byte{0xFF}
|
||||
err := gw.CompleteBridge(nonExistent, [][]byte{[]byte("sig")})
|
||||
if err != ErrRequestNotFound {
|
||||
t.Errorf("Expected ErrRequestNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompleteBridgeAlreadyDone tests double completion
|
||||
func TestCompleteBridgeAlreadyDone(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// Setup and complete
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
|
||||
_ = gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
|
||||
|
||||
// Try to complete again
|
||||
err := gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
|
||||
if err != ErrRequestAlreadyDone {
|
||||
t.Errorf("Expected ErrRequestAlreadyDone, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompleteBridgeExpired tests expired request
|
||||
func TestCompleteBridgeExpired(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// Setup
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 1, nil) // Already expired
|
||||
|
||||
err := gw.CompleteBridge(request.ID, [][]byte{[]byte("sig")})
|
||||
if err != ErrRequestExpired {
|
||||
t.Errorf("Expected ErrRequestExpired, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundExpired tests refunding expired requests
|
||||
func TestRefundExpired(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// Setup
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e21())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
request, _ := gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 1, nil) // Already expired
|
||||
|
||||
err := gw.RefundExpired(request.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("RefundExpired failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify status
|
||||
refundedRequest, _ := gw.GetRequest(request.ID)
|
||||
if refundedRequest.Status != StatusRefunded {
|
||||
t.Errorf("Expected refunded status, got %v", refundedRequest.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddLiquidity tests adding liquidity
|
||||
func TestAddLiquidity(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
amount := e20()
|
||||
|
||||
position, err := gw.AddLiquidity(provider, token, ChainEthereum, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("AddLiquidity failed: %v", err)
|
||||
}
|
||||
|
||||
if position == nil {
|
||||
t.Fatal("Expected non-nil position")
|
||||
}
|
||||
if position.Provider != provider {
|
||||
t.Error("Provider mismatch")
|
||||
}
|
||||
if position.Amount.Cmp(amount) != 0 {
|
||||
t.Errorf("Expected amount %v, got %v", amount, position.Amount)
|
||||
}
|
||||
|
||||
// Verify pool state
|
||||
pool := gw.Pools[ChainEthereum][token]
|
||||
if pool == nil {
|
||||
t.Fatal("Pool not created")
|
||||
}
|
||||
if pool.TotalLiq.Cmp(amount) != 0 {
|
||||
t.Errorf("Expected total liquidity %v, got %v", amount, pool.TotalLiq)
|
||||
}
|
||||
if pool.Available.Cmp(amount) != 0 {
|
||||
t.Errorf("Expected available %v, got %v", amount, pool.Available)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddLiquidityChainNotSupported tests error for unsupported chain
|
||||
func TestAddLiquidityChainNotSupported(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
_, err := gw.AddLiquidity(provider, token, 99999, big.NewInt(1e18))
|
||||
if err != ErrChainNotSupported {
|
||||
t.Errorf("Expected ErrChainNotSupported, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddLiquidityMultiple tests adding liquidity multiple times
|
||||
func TestAddLiquidityMultiple(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
|
||||
|
||||
pool := gw.Pools[ChainEthereum][token]
|
||||
expected := new(big.Int).Mul(big.NewInt(2), e20())
|
||||
if pool.TotalLiq.Cmp(expected) != 0 {
|
||||
t.Errorf("Expected total %v, got %v", expected, pool.TotalLiq)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveLiquidity tests removing liquidity
|
||||
func TestRemoveLiquidity(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e20())
|
||||
|
||||
total, err := gw.RemoveLiquidity(provider, token, ChainEthereum, fiveE19())
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveLiquidity failed: %v", err)
|
||||
}
|
||||
|
||||
// Should return amount + fees
|
||||
if total.Cmp(fiveE19()) < 0 {
|
||||
t.Errorf("Expected at least %v, got %v", fiveE19(), total)
|
||||
}
|
||||
|
||||
// Verify pool updated
|
||||
pool := gw.Pools[ChainEthereum][token]
|
||||
if pool.TotalLiq.Cmp(fiveE19()) != 0 {
|
||||
t.Errorf("Expected remaining %v, got %v", fiveE19(), pool.TotalLiq)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveLiquidityInsufficientBalance tests removing more than deposited
|
||||
func TestRemoveLiquidityInsufficientBalance(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
provider := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, big.NewInt(1e18))
|
||||
|
||||
_, err := gw.RemoveLiquidity(provider, token, ChainEthereum, e20()) // More than deposited
|
||||
if err != ErrInsufficientLiquidity {
|
||||
t.Errorf("Expected ErrInsufficientLiquidity, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetRequest tests request retrieval
|
||||
func TestGetRequest(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
nonExistent := [32]byte{0xFF}
|
||||
_, err := gw.GetRequest(nonExistent)
|
||||
if err != ErrRequestNotFound {
|
||||
t.Errorf("Expected ErrRequestNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculateFee tests fee calculation
|
||||
func TestCalculateFee(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
amount *big.Int
|
||||
minFee *big.Int
|
||||
}{
|
||||
{"Small amount", big.NewInt(1e17), gw.Config.MinFee}, // Fee would be below min
|
||||
{"Large amount", e22(), gw.Config.MaxFee}, // Fee would exceed max
|
||||
{"Medium amount", e19(), nil}, // Normal calculation
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fee := gw.calculateFee(tt.amount)
|
||||
if fee.Sign() <= 0 {
|
||||
t.Error("Expected positive fee")
|
||||
}
|
||||
if gw.Config.MinFee.Sign() > 0 && fee.Cmp(gw.Config.MinFee) < 0 {
|
||||
t.Errorf("Fee %v below minimum %v", fee, gw.Config.MinFee)
|
||||
}
|
||||
if gw.Config.MaxFee.Sign() > 0 && fee.Cmp(gw.Config.MaxFee) > 0 {
|
||||
t.Errorf("Fee %v above maximum %v", fee, gw.Config.MaxFee)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNonceIncrement tests nonce handling
|
||||
func TestNonceIncrement(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e25())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, e24())
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
|
||||
for i := range uint64(5) {
|
||||
_, _ = gw.InitiateBridge(sender, sender, token, big.NewInt(1e18), ChainLux, ChainEthereum, 0, nil)
|
||||
if gw.Nonces[sender] != i+1 {
|
||||
t.Errorf("Expected nonce %d, got %d", i+1, gw.Nonces[sender])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSignerThreshold tests threshold calculation
|
||||
func TestSignerThreshold(t *testing.T) {
|
||||
gw := NewBridgeGateway()
|
||||
|
||||
// No signers
|
||||
threshold := gw.getThreshold()
|
||||
if threshold != 1 {
|
||||
t.Errorf("Expected threshold 1 with no signers, got %d", threshold)
|
||||
}
|
||||
|
||||
// Add signers
|
||||
gw.SignerSet.Signers = make([]*SignerInfo, 10)
|
||||
threshold = gw.getThreshold()
|
||||
expected := uint32((10 * 2 / 3) + 1) // 2/3 + 1 = 7
|
||||
if threshold != expected {
|
||||
t.Errorf("Expected threshold %d, got %d", expected, threshold)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
|
||||
func BenchmarkInitiateBridge(b *testing.B) {
|
||||
gw := NewBridgeGateway()
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
_ = gw.RegisterToken(token, 18, "TEST", "Test", big.NewInt(1e17), e24(), e30())
|
||||
provider := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, new(big.Int).Mul(e24(), big.NewInt(int64(b.N)+1)))
|
||||
|
||||
sender := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
amount := big.NewInt(1e18)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = gw.InitiateBridge(sender, sender, token, amount, ChainLux, ChainEthereum, 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCalculateFee(b *testing.B) {
|
||||
gw := NewBridgeGateway()
|
||||
amount := e20()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = gw.calculateFee(amount)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddLiquidity(b *testing.B) {
|
||||
gw := NewBridgeGateway()
|
||||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||||
amount := big.NewInt(1e18)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
provider := common.BigToAddress(big.NewInt(int64(i)))
|
||||
_, _ = gw.AddLiquidity(provider, token, ChainEthereum, amount)
|
||||
}
|
||||
}
|
||||
@@ -161,18 +161,3 @@ func TestStateRegistry_RejectsLongName(t *testing.T) {
|
||||
t.Fatalf("expected ErrChainNameTooLong, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGateway_RegistryDriven(t *testing.T) {
|
||||
// A gateway built over an ad-hoc registry only supports the ids in it.
|
||||
r := NewStatic([]Chain{
|
||||
{ID: 42, Name: "answer", EVM: true},
|
||||
})
|
||||
gw := NewBridgeGatewayWithRegistry(r)
|
||||
|
||||
if !gw.Supports(42) {
|
||||
t.Fatal("expected 42 to be supported")
|
||||
}
|
||||
if gw.Supports(96369) {
|
||||
t.Fatal("expected 96369 to be unsupported (not in registry)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// BridgeSigner provides the interface to B-Chain MPC signing
|
||||
// This precompile at 0x0445 allows EVM contracts to request MPC signatures
|
||||
type BridgeSigner struct {
|
||||
// Signer set management
|
||||
SignerSet *SignerSet
|
||||
PendingSigns map[[32]byte]*SigningSession
|
||||
|
||||
// B-Chain connection (in production, would be cross-chain call)
|
||||
BChainEndpoint string
|
||||
|
||||
// Configuration
|
||||
SignTimeout time.Duration
|
||||
MaxPendingSigns int
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// SigningSession represents an active signing request
|
||||
type SigningSession struct {
|
||||
SessionID [32]byte
|
||||
MessageHash [32]byte
|
||||
RequestedBy common.Address
|
||||
RequestedAt uint64
|
||||
ExpiresAt uint64
|
||||
Status SigningStatus
|
||||
Signatures map[[20]byte][]byte // NodeID -> signature
|
||||
FinalSig []byte // Combined threshold signature
|
||||
CallbackAddr common.Address // Contract to call when done
|
||||
CallbackData []byte // Data for callback
|
||||
}
|
||||
|
||||
// SigningStatus represents the status of a signing session
|
||||
type SigningStatus uint8
|
||||
|
||||
const (
|
||||
SigningPending SigningStatus = iota
|
||||
SigningInProgress
|
||||
SigningComplete
|
||||
SigningFailed
|
||||
SigningExpired
|
||||
)
|
||||
|
||||
// NewBridgeSigner creates a new bridge signer interface
|
||||
func NewBridgeSigner() *BridgeSigner {
|
||||
return &BridgeSigner{
|
||||
SignerSet: &SignerSet{
|
||||
Signers: make([]*SignerInfo, 0),
|
||||
Waitlist: make([][20]byte, 0),
|
||||
Threshold: 67,
|
||||
},
|
||||
PendingSigns: make(map[[32]byte]*SigningSession),
|
||||
SignTimeout: 5 * time.Minute,
|
||||
MaxPendingSigns: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
// RequestSignature requests an MPC signature from the signer set
|
||||
func (bs *BridgeSigner) RequestSignature(
|
||||
requester common.Address,
|
||||
messageHash [32]byte,
|
||||
callbackAddr common.Address,
|
||||
callbackData []byte,
|
||||
) ([32]byte, error) {
|
||||
bs.mu.Lock()
|
||||
defer bs.mu.Unlock()
|
||||
|
||||
if len(bs.PendingSigns) >= bs.MaxPendingSigns {
|
||||
return [32]byte{}, errors.New("too many pending signing requests")
|
||||
}
|
||||
|
||||
if len(bs.SignerSet.Signers) == 0 {
|
||||
return [32]byte{}, errors.New("no active signers")
|
||||
}
|
||||
|
||||
// Generate session ID
|
||||
now := uint64(time.Now().Unix())
|
||||
sessionData := append(messageHash[:], requester.Bytes()...)
|
||||
sessionData = append(sessionData, big.NewInt(int64(now)).Bytes()...)
|
||||
sessionID := sha256.Sum256(sessionData)
|
||||
|
||||
session := &SigningSession{
|
||||
SessionID: sessionID,
|
||||
MessageHash: messageHash,
|
||||
RequestedBy: requester,
|
||||
RequestedAt: now,
|
||||
ExpiresAt: now + uint64(bs.SignTimeout.Seconds()),
|
||||
Status: SigningPending,
|
||||
Signatures: make(map[[20]byte][]byte),
|
||||
CallbackAddr: callbackAddr,
|
||||
CallbackData: callbackData,
|
||||
}
|
||||
|
||||
bs.PendingSigns[sessionID] = session
|
||||
|
||||
// In production, this would send a message to B-Chain
|
||||
// to initiate MPC signing protocol
|
||||
go bs.initiateSigningProtocol(session)
|
||||
|
||||
return sessionID, nil
|
||||
}
|
||||
|
||||
// GetSignature retrieves a completed signature
|
||||
func (bs *BridgeSigner) GetSignature(sessionID [32]byte) ([]byte, SigningStatus, error) {
|
||||
bs.mu.RLock()
|
||||
defer bs.mu.RUnlock()
|
||||
|
||||
session := bs.PendingSigns[sessionID]
|
||||
if session == nil {
|
||||
return nil, 0, errors.New("signing session not found")
|
||||
}
|
||||
|
||||
return session.FinalSig, session.Status, nil
|
||||
}
|
||||
|
||||
// SubmitPartialSignature allows a signer to submit their signature share
|
||||
func (bs *BridgeSigner) SubmitPartialSignature(
|
||||
sessionID [32]byte,
|
||||
signerNodeID [20]byte,
|
||||
signature []byte,
|
||||
) error {
|
||||
bs.mu.Lock()
|
||||
defer bs.mu.Unlock()
|
||||
|
||||
session := bs.PendingSigns[sessionID]
|
||||
if session == nil {
|
||||
return errors.New("signing session not found")
|
||||
}
|
||||
|
||||
if session.Status != SigningPending && session.Status != SigningInProgress {
|
||||
return errors.New("signing session not accepting signatures")
|
||||
}
|
||||
|
||||
if uint64(time.Now().Unix()) > session.ExpiresAt {
|
||||
session.Status = SigningExpired
|
||||
return errors.New("signing session expired")
|
||||
}
|
||||
|
||||
// Verify signer is in the active set
|
||||
if !bs.isActiveSigner(signerNodeID) {
|
||||
return ErrUnauthorizedSigner
|
||||
}
|
||||
|
||||
// Store partial signature
|
||||
session.Signatures[signerNodeID] = signature
|
||||
session.Status = SigningInProgress
|
||||
|
||||
// Check if we have enough signatures
|
||||
threshold := bs.getThreshold()
|
||||
if uint32(len(session.Signatures)) >= threshold {
|
||||
// Combine signatures
|
||||
finalSig, err := bs.combineSignatures(session)
|
||||
if err != nil {
|
||||
session.Status = SigningFailed
|
||||
return err
|
||||
}
|
||||
|
||||
session.FinalSig = finalSig
|
||||
session.Status = SigningComplete
|
||||
|
||||
// Execute callback if specified
|
||||
if session.CallbackAddr != (common.Address{}) {
|
||||
go bs.executeCallback(session)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSignerSet returns the current signer set information
|
||||
func (bs *BridgeSigner) GetSignerSet() (*SignerSet, error) {
|
||||
bs.mu.RLock()
|
||||
defer bs.mu.RUnlock()
|
||||
|
||||
return bs.SignerSet, nil
|
||||
}
|
||||
|
||||
// GetPublicKey returns the combined threshold public key
|
||||
func (bs *BridgeSigner) GetPublicKey() ([]byte, error) {
|
||||
bs.mu.RLock()
|
||||
defer bs.mu.RUnlock()
|
||||
|
||||
if bs.SignerSet == nil || len(bs.SignerSet.PublicKey) == 0 {
|
||||
return nil, errors.New("no public key available")
|
||||
}
|
||||
|
||||
return bs.SignerSet.PublicKey, nil
|
||||
}
|
||||
|
||||
// RegisterSigner registers a new signer (implements LP-333 opt-in model)
|
||||
func (bs *BridgeSigner) RegisterSigner(
|
||||
nodeID [20]byte,
|
||||
evmAddress common.Address,
|
||||
publicKeyShare []byte,
|
||||
bond *big.Int,
|
||||
) error {
|
||||
bs.mu.Lock()
|
||||
defer bs.mu.Unlock()
|
||||
|
||||
// Check minimum bond
|
||||
if bond.Cmp(MinSignerBond) < 0 {
|
||||
return ErrInsufficientBond
|
||||
}
|
||||
|
||||
// Check if already a signer
|
||||
for _, signer := range bs.SignerSet.Signers {
|
||||
if signer.NodeID == nodeID {
|
||||
return ErrAlreadySigner
|
||||
}
|
||||
}
|
||||
|
||||
signer := &SignerInfo{
|
||||
NodeID: nodeID,
|
||||
Address: evmAddress,
|
||||
PublicKey: publicKeyShare,
|
||||
Bond: bond,
|
||||
JoinedAt: uint64(time.Now().Unix()),
|
||||
LastActive: uint64(time.Now().Unix()),
|
||||
SignCount: 0,
|
||||
SlashCount: 0,
|
||||
Status: SignerActive,
|
||||
}
|
||||
|
||||
// First 100 validators join without reshare (LP-333)
|
||||
if len(bs.SignerSet.Signers) < MaxSigners {
|
||||
bs.SignerSet.Signers = append(bs.SignerSet.Signers, signer)
|
||||
bs.updateThreshold()
|
||||
} else {
|
||||
// Add to waitlist
|
||||
signer.Status = SignerWaitlist
|
||||
bs.SignerSet.Waitlist = append(bs.SignerSet.Waitlist, nodeID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSigner removes a signer from the set
|
||||
func (bs *BridgeSigner) RemoveSigner(nodeID [20]byte) error {
|
||||
bs.mu.Lock()
|
||||
defer bs.mu.Unlock()
|
||||
|
||||
found := false
|
||||
newSigners := make([]*SignerInfo, 0, len(bs.SignerSet.Signers))
|
||||
|
||||
for _, signer := range bs.SignerSet.Signers {
|
||||
if signer.NodeID != nodeID {
|
||||
newSigners = append(newSigners, signer)
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return ErrSignerNotFound
|
||||
}
|
||||
|
||||
bs.SignerSet.Signers = newSigners
|
||||
bs.updateThreshold()
|
||||
|
||||
// Promote from waitlist if available
|
||||
if len(bs.SignerSet.Waitlist) > 0 {
|
||||
// This would trigger a reshare in production
|
||||
bs.SignerSet.Epoch++
|
||||
bs.SignerSet.LastReshare = uint64(time.Now().Unix())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SlashSigner reduces a signer's bond due to misbehavior
|
||||
func (bs *BridgeSigner) SlashSigner(nodeID [20]byte, slashPercent uint32) error {
|
||||
bs.mu.Lock()
|
||||
defer bs.mu.Unlock()
|
||||
|
||||
for _, signer := range bs.SignerSet.Signers {
|
||||
if signer.NodeID == nodeID {
|
||||
// Calculate slash amount
|
||||
slashAmount := new(big.Int).Mul(signer.Bond, big.NewInt(int64(slashPercent)))
|
||||
slashAmount.Div(slashAmount, big.NewInt(100))
|
||||
|
||||
// Reduce bond
|
||||
signer.Bond.Sub(signer.Bond, slashAmount)
|
||||
signer.SlashCount++
|
||||
|
||||
// Remove if bond drops below minimum
|
||||
if signer.Bond.Cmp(MinSignerBond) < 0 {
|
||||
signer.Status = SignerSlashed
|
||||
return bs.RemoveSigner(nodeID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrSignerNotFound
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (bs *BridgeSigner) isActiveSigner(nodeID [20]byte) bool {
|
||||
for _, signer := range bs.SignerSet.Signers {
|
||||
if signer.NodeID == nodeID && signer.Status == SignerActive {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (bs *BridgeSigner) getThreshold() uint32 {
|
||||
if len(bs.SignerSet.Signers) == 0 {
|
||||
return 1
|
||||
}
|
||||
// 2/3 + 1 of active signers
|
||||
return (uint32(len(bs.SignerSet.Signers)) * 2 / 3) + 1
|
||||
}
|
||||
|
||||
func (bs *BridgeSigner) updateThreshold() {
|
||||
bs.SignerSet.Threshold = bs.getThreshold()
|
||||
}
|
||||
|
||||
func (bs *BridgeSigner) initiateSigningProtocol(session *SigningSession) {
|
||||
// Signing protocol initiation is handled by the Warp messaging layer.
|
||||
// This method is called for side effects (session state update) and
|
||||
// the actual cross-chain MPC coordination happens in the B-Chain's
|
||||
// threshold coordinator via luxfi/threshold protocols.
|
||||
}
|
||||
|
||||
func (bs *BridgeSigner) combineSignatures(session *SigningSession) ([]byte, error) {
|
||||
// Concatenate signature shares for the threshold coordinator to combine.
|
||||
// The actual FROST/CGGMP21/Corona combination is performed by the
|
||||
// B-Chain's MPC coordinator in luxfi/threshold.
|
||||
if len(session.Signatures) == 0 {
|
||||
return nil, fmt.Errorf("no signatures to combine")
|
||||
}
|
||||
var combined []byte
|
||||
for _, sig := range session.Signatures {
|
||||
combined = append(combined, sig...)
|
||||
}
|
||||
|
||||
return combined, nil
|
||||
}
|
||||
|
||||
func (bs *BridgeSigner) executeCallback(session *SigningSession) {
|
||||
// In production, this would call the callback contract
|
||||
// with the signature result
|
||||
}
|
||||
|
||||
// VerifyThresholdSignature verifies a threshold signature against the public key
|
||||
func (bs *BridgeSigner) VerifyThresholdSignature(
|
||||
messageHash [32]byte,
|
||||
signature []byte,
|
||||
) (bool, error) {
|
||||
bs.mu.RLock()
|
||||
defer bs.mu.RUnlock()
|
||||
|
||||
if len(bs.SignerSet.PublicKey) == 0 {
|
||||
return false, errors.New("no public key available")
|
||||
}
|
||||
|
||||
// In production, verify using the appropriate threshold scheme
|
||||
// This would call into lux/threshold package
|
||||
return len(signature) > 0, nil
|
||||
}
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// Precompile addresses for B-Chain bridge operations
|
||||
const (
|
||||
// Bridge core operations
|
||||
BridgeGatewayAddress = "0x0440" // Main bridge gateway
|
||||
BridgeRouterAddress = "0x0441" // Cross-chain routing
|
||||
BridgeVerifierAddress = "0x0442" // Message verification
|
||||
BridgeLiquidityAddress = "0x0443" // Liquidity pools
|
||||
BridgeFeeManagerAddress = "0x0444" // Fee management
|
||||
BridgeSignerAddress = "0x0445" // MPC signer interface
|
||||
|
||||
// Gas costs
|
||||
GasBridgeInitiate = uint64(100000) // Initiate bridge transfer
|
||||
GasBridgeComplete = uint64(50000) // Complete bridge on destination
|
||||
GasBridgeVerify = uint64(25000) // Verify bridge message
|
||||
GasBridgeGetStatus = uint64(5000) // Query bridge status
|
||||
GasBridgeAddLiq = uint64(75000) // Add liquidity
|
||||
GasBridgeRemoveLiq = uint64(75000) // Remove liquidity
|
||||
GasSignerGetPubKey = uint64(10000) // Get MPC public key
|
||||
GasSignerRequestSign = uint64(150000) // Request MPC signature
|
||||
)
|
||||
|
||||
// Supported chain IDs
|
||||
const (
|
||||
ChainLux uint32 = 96369 // Lux mainnet C-Chain
|
||||
ChainLuxTest uint32 = 96368 // Lux testnet
|
||||
ChainHanzo uint32 = 36963 // Hanzo AI mainnet
|
||||
ChainHanzoTest uint32 = 36962 // Hanzo testnet
|
||||
ChainZoo uint32 = 200200 // Zoo mainnet
|
||||
ChainZooTest uint32 = 200201 // Zoo testnet
|
||||
ChainSPC uint32 = 36911 // SPC mainnet
|
||||
ChainSPCTest uint32 = 36910 // SPC testnet
|
||||
ChainEthereum uint32 = 1 // Ethereum mainnet
|
||||
ChainArbitrum uint32 = 42161 // Arbitrum One
|
||||
ChainOptimism uint32 = 10 // Optimism
|
||||
ChainBase uint32 = 8453 // Base
|
||||
ChainPolygon uint32 = 137 // Polygon PoS
|
||||
ChainBSC uint32 = 56 // BNB Smart Chain
|
||||
ChainAvalanche uint32 = 43114 // Avalanche C-Chain
|
||||
ChainPars uint32 = 6133 // Pars Network mainnet
|
||||
ChainParsTest uint32 = 6132 // Pars Network testnet
|
||||
|
||||
// Non-EVM chains (use virtual chain IDs for bridge tracking)
|
||||
// These don't have native EVM chain IDs but are tracked by the bridge
|
||||
ChainSolana uint32 = 900001 // Solana mainnet
|
||||
ChainBitcoin uint32 = 900002 // Bitcoin mainnet
|
||||
ChainXRP uint32 = 900003 // XRP Ledger mainnet
|
||||
ChainTON uint32 = 900004 // TON mainnet
|
||||
)
|
||||
|
||||
// BridgeRequest represents a cross-chain transfer request
|
||||
type BridgeRequest struct {
|
||||
ID [32]byte // Unique request ID
|
||||
Sender common.Address // Source chain sender
|
||||
Recipient common.Address // Destination chain recipient
|
||||
Token common.Address // Token address (address(0) for native)
|
||||
Amount *big.Int // Amount to bridge
|
||||
SourceChain uint32 // Source chain ID
|
||||
DestChain uint32 // Destination chain ID
|
||||
Nonce uint64 // Sender nonce for replay protection
|
||||
Deadline uint64 // Timestamp deadline
|
||||
Data []byte // Optional calldata for recipient
|
||||
Status BridgeStatus // Current status
|
||||
SourceTxHash common.Hash // Source chain transaction hash
|
||||
DestTxHash common.Hash // Destination chain transaction hash
|
||||
Signatures [][]byte // MPC signatures
|
||||
CreatedAt uint64 // Creation timestamp
|
||||
CompletedAt uint64 // Completion timestamp
|
||||
}
|
||||
|
||||
// BridgeStatus represents the status of a bridge request
|
||||
type BridgeStatus uint8
|
||||
|
||||
const (
|
||||
StatusPending BridgeStatus = iota
|
||||
StatusSigning
|
||||
StatusSigned
|
||||
StatusRelaying
|
||||
StatusCompleted
|
||||
StatusFailed
|
||||
StatusExpired
|
||||
StatusRefunded
|
||||
)
|
||||
|
||||
// BridgedToken represents a token that can be bridged
|
||||
type BridgedToken struct {
|
||||
LocalAddress common.Address // Address on this chain
|
||||
RemoteAddress map[uint32]common.Address // Address on remote chains
|
||||
Decimals uint8 // Token decimals
|
||||
Symbol string // Token symbol
|
||||
Name string // Token name
|
||||
MinBridge *big.Int // Minimum bridge amount
|
||||
MaxBridge *big.Int // Maximum bridge amount (per tx)
|
||||
DailyLimit *big.Int // Daily bridge limit
|
||||
BridgedToday *big.Int // Amount bridged today
|
||||
LastReset uint64 // Last daily reset timestamp
|
||||
Enabled bool // Whether bridging is enabled
|
||||
}
|
||||
|
||||
// LiquidityPool represents a bridge liquidity pool
|
||||
type LiquidityPool struct {
|
||||
Token common.Address // Pool token
|
||||
ChainID uint32 // Chain ID
|
||||
TotalLiq *big.Int // Total liquidity
|
||||
Available *big.Int // Available liquidity (not in transit)
|
||||
Providers map[common.Address]*LPPosition
|
||||
FeeRate uint32 // Fee in basis points (100 = 1%)
|
||||
TotalFees *big.Int // Total fees collected
|
||||
}
|
||||
|
||||
// LPPosition represents a liquidity provider position
|
||||
type LPPosition struct {
|
||||
Provider common.Address
|
||||
Amount *big.Int
|
||||
ShareRatio *big.Int // Share of pool (scaled by 1e18)
|
||||
DepositTime uint64
|
||||
PendingFees *big.Int
|
||||
}
|
||||
|
||||
// BridgeMessage is the message format for cross-chain communication
|
||||
type BridgeMessage struct {
|
||||
Version uint8 // Message version
|
||||
MessageType uint8 // Type of message
|
||||
SourceChain uint32 // Source chain ID
|
||||
DestChain uint32 // Destination chain ID
|
||||
Nonce uint64 // Message nonce
|
||||
Sender common.Address // Original sender
|
||||
Recipient common.Address // Destination recipient
|
||||
Token common.Address // Token address
|
||||
Amount *big.Int // Amount
|
||||
Data []byte // Additional data
|
||||
Timestamp uint64 // Message timestamp
|
||||
}
|
||||
|
||||
// MessageType constants
|
||||
const (
|
||||
MsgTypeTransfer uint8 = 1 // Token transfer
|
||||
MsgTypeMint uint8 = 2 // Mint wrapped token
|
||||
MsgTypeBurn uint8 = 3 // Burn wrapped token
|
||||
MsgTypeUnlock uint8 = 4 // Unlock native token
|
||||
MsgTypeLiquidity uint8 = 5 // Liquidity operation
|
||||
MsgTypeGovernance uint8 = 6 // Governance message
|
||||
MsgTypeEmergency uint8 = 7 // Emergency pause/unpause
|
||||
)
|
||||
|
||||
// SignerInfo represents an MPC signer in the bridge set
|
||||
type SignerInfo struct {
|
||||
NodeID [20]byte // Validator node ID
|
||||
Address common.Address // EVM address
|
||||
PublicKey []byte // MPC public key share
|
||||
Bond *big.Int // Staked bond (min 100M LUX)
|
||||
JoinedAt uint64 // When joined signer set
|
||||
LastActive uint64 // Last activity timestamp
|
||||
SignCount uint64 // Total signatures produced
|
||||
SlashCount uint32 // Times slashed
|
||||
Status SignerStatus
|
||||
}
|
||||
|
||||
// SignerStatus represents MPC signer status
|
||||
type SignerStatus uint8
|
||||
|
||||
const (
|
||||
SignerActive SignerStatus = iota
|
||||
SignerWaitlist
|
||||
SignerSlashed
|
||||
SignerExited
|
||||
)
|
||||
|
||||
// SignerSet represents the current MPC signer set
|
||||
type SignerSet struct {
|
||||
Signers []*SignerInfo // Active signers (max 100)
|
||||
Waitlist [][20]byte // Waitlisted node IDs
|
||||
Threshold uint32 // Required signatures (2/3 of signers)
|
||||
Epoch uint64 // Current epoch
|
||||
PublicKey []byte // Combined threshold public key
|
||||
LastReshare uint64 // Last reshare timestamp
|
||||
}
|
||||
|
||||
// BridgeFeeConfig represents fee configuration
|
||||
type BridgeFeeConfig struct {
|
||||
BaseFee *big.Int // Base fee per bridge
|
||||
PercentFee uint32 // Fee percentage (basis points)
|
||||
MinFee *big.Int // Minimum fee
|
||||
MaxFee *big.Int // Maximum fee
|
||||
LiquidityFee uint32 // Fee to liquidity providers (basis points)
|
||||
ProtocolFee uint32 // Protocol fee (basis points)
|
||||
EmergencyPenalty uint32 // Emergency withdrawal penalty (basis points)
|
||||
}
|
||||
|
||||
// Bridge errors
|
||||
var (
|
||||
ErrBridgeDisabled = errors.New("bridge is disabled")
|
||||
ErrTokenNotSupported = errors.New("token not supported for bridging")
|
||||
ErrChainNotSupported = errors.New("destination chain not supported")
|
||||
ErrAmountTooLow = errors.New("amount below minimum")
|
||||
ErrAmountTooHigh = errors.New("amount exceeds maximum")
|
||||
ErrDailyLimitExceeded = errors.New("daily bridge limit exceeded")
|
||||
ErrInsufficientLiquidity = errors.New("insufficient bridge liquidity")
|
||||
ErrInvalidSignature = errors.New("invalid bridge signature")
|
||||
ErrSignatureThreshold = errors.New("signature threshold not met")
|
||||
ErrRequestNotFound = errors.New("bridge request not found")
|
||||
ErrRequestExpired = errors.New("bridge request expired")
|
||||
ErrRequestAlreadyDone = errors.New("bridge request already completed")
|
||||
ErrInvalidMessage = errors.New("invalid bridge message")
|
||||
ErrUnauthorizedSigner = errors.New("unauthorized signer")
|
||||
ErrSignerNotFound = errors.New("signer not found")
|
||||
ErrInsufficientBond = errors.New("insufficient signer bond")
|
||||
ErrAlreadySigner = errors.New("already in signer set")
|
||||
ErrSignerSetFull = errors.New("signer set is full")
|
||||
ErrInvalidNonce = errors.New("invalid nonce")
|
||||
ErrReplayAttack = errors.New("replay attack detected")
|
||||
)
|
||||
|
||||
// MinSignerBond is the minimum bond required to be a signer (100M LUX)
|
||||
var MinSignerBond = new(big.Int).Mul(big.NewInt(100_000_000), big.NewInt(1e18))
|
||||
|
||||
// MaxSigners is the maximum number of active signers
|
||||
const MaxSigners = 100
|
||||
Reference in New Issue
Block a user