mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
553 lines
13 KiB
Go
553 lines
13 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
//go:build test
|
|
|
|
package x
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/math/set"
|
|
"github.com/luxfi/node/vms/components/verify"
|
|
"github.com/luxfi/node/wallet/chain/x/builder"
|
|
"github.com/luxfi/node/wallet/network/primary/common/utxotest"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/utxo/nftfx"
|
|
"github.com/luxfi/utxo/propertyfx"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
)
|
|
|
|
var (
|
|
testKeys = secp256k1.TestKeys()
|
|
|
|
// We hard-code [utxoAssetID] and [chainAssetID] to make
|
|
// ordering of UTXOs generated by [testUTXOsList] is reproducible
|
|
utxoAssetID = ids.Empty.Prefix(1789)
|
|
xChainID = ids.Empty.Prefix(2021)
|
|
nftAssetID = ids.Empty.Prefix(2022)
|
|
propertyAssetID = ids.Empty.Prefix(2023)
|
|
|
|
testContext = &builder.Context{
|
|
NetworkID: constants.UnitTestID,
|
|
BlockchainID: xChainID,
|
|
UTXOAssetID: utxoAssetID,
|
|
BaseTxFee: constants.MicroLux,
|
|
CreateAssetTxFee: 99 * constants.MilliLux,
|
|
}
|
|
)
|
|
|
|
// These tests create and sign a tx, then verify that utxos included
|
|
// in the tx are exactly necessary to pay fees for it
|
|
|
|
func TestBaseTx(t *testing.T) {
|
|
var (
|
|
require = require.New(t)
|
|
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
outputsToMove = []*lux.TransferableOutput{{
|
|
Asset: lux.Asset{ID: utxoAssetID},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: 7 * constants.Lux,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxoAddr},
|
|
},
|
|
},
|
|
}}
|
|
)
|
|
|
|
utx, err := builder.NewBaseTx(
|
|
outputsToMove,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 2)
|
|
require.Len(outs, 2)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() - outs[1].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
require.Equal(outputsToMove[0], outs[1])
|
|
}
|
|
|
|
func TestCreateAssetTx(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
assetName = "Team Rocket"
|
|
symbol = "TR"
|
|
denomination uint8 = 0
|
|
initialState = map[uint32][]verify.State{
|
|
0: {
|
|
&secp256k1fx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()},
|
|
},
|
|
}, &secp256k1fx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()},
|
|
},
|
|
},
|
|
},
|
|
1: {
|
|
&nftfx.MintOutput{
|
|
GroupID: 1,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[1].PublicKey().Address()},
|
|
},
|
|
},
|
|
&nftfx.MintOutput{
|
|
GroupID: 2,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[1].PublicKey().Address()},
|
|
},
|
|
},
|
|
},
|
|
2: {
|
|
&propertyfx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[2].PublicKey().Address()},
|
|
},
|
|
},
|
|
&propertyfx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{testKeys[2].PublicKey().Address()},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
utx, err := builder.NewCreateAssetTx(
|
|
assetName,
|
|
symbol,
|
|
denomination,
|
|
initialState,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 2)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.CreateAssetTxFee
|
|
consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestMintNFTOperation(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
payload = []byte{'h', 'e', 'l', 'l', 'o'}
|
|
NFTOwner = &secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxoAddr},
|
|
}
|
|
)
|
|
|
|
utx, err := builder.NewOperationTxMintNFT(
|
|
nftAssetID,
|
|
payload,
|
|
[]*secp256k1fx.OutputOwners{NFTOwner},
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 1)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := ins[0].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestMintFTOperation(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
outputs = map[ids.ID]*secp256k1fx.TransferOutput{
|
|
nftAssetID: {
|
|
Amt: 1,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxoAddr},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
utx, err := builder.NewOperationTxMintFT(
|
|
outputs,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 1)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := ins[0].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestMintPropertyOperation(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
propertyOwner = &secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxoAddr},
|
|
}
|
|
)
|
|
|
|
utx, err := builder.NewOperationTxMintProperty(
|
|
propertyAssetID,
|
|
propertyOwner,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 1)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := ins[0].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestBurnPropertyOperation(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
)
|
|
|
|
utx, err := builder.NewOperationTxBurnProperty(
|
|
propertyAssetID,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 1)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := ins[0].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestImportTx(t *testing.T) {
|
|
var (
|
|
require = require.New(t)
|
|
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
sourceChainID = ids.GenerateTestID()
|
|
importedUTXOs = utxos[:1]
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
sourceChainID: importedUTXOs,
|
|
},
|
|
)
|
|
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
importKey = testKeys[0]
|
|
importTo = &secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{
|
|
importKey.Address(),
|
|
},
|
|
}
|
|
)
|
|
|
|
utx, err := builder.NewImportTx(
|
|
sourceChainID,
|
|
importTo,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
importedIns := utx.ImportedIns
|
|
require.Empty(ins)
|
|
require.Len(importedIns, 1)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee
|
|
consumed := importedIns[0].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
}
|
|
|
|
func TestExportTx(t *testing.T) {
|
|
var (
|
|
require = require.New(t)
|
|
|
|
// backend
|
|
utxosKey = testKeys[1]
|
|
utxos = makeTestUTXOs(utxosKey)
|
|
genericBackend = utxotest.NewDeterministicChainUTXOs(
|
|
t,
|
|
map[ids.ID][]*lux.UTXO{
|
|
xChainID: utxos,
|
|
},
|
|
)
|
|
backend = NewBackend(testContext, genericBackend)
|
|
|
|
// builder
|
|
utxoAddr = utxosKey.Address()
|
|
builder = builder.New(set.Of(utxoAddr), testContext, backend)
|
|
|
|
// data to build the transaction
|
|
netID = ids.GenerateTestID()
|
|
exportedOutputs = []*lux.TransferableOutput{{
|
|
Asset: lux.Asset{ID: utxoAssetID},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: 7 * constants.Lux,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxoAddr},
|
|
},
|
|
},
|
|
}}
|
|
)
|
|
|
|
utx, err := builder.NewExportTx(
|
|
netID,
|
|
exportedOutputs,
|
|
)
|
|
require.NoError(err)
|
|
|
|
// check UTXOs selection and fee financing
|
|
ins := utx.Ins
|
|
outs := utx.Outs
|
|
require.Len(ins, 2)
|
|
require.Len(outs, 1)
|
|
|
|
expectedConsumed := testContext.BaseTxFee + exportedOutputs[0].Out.Amount()
|
|
consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount()
|
|
require.Equal(expectedConsumed, consumed)
|
|
require.Equal(utx.ExportedOuts, exportedOutputs)
|
|
}
|
|
|
|
func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*lux.UTXO {
|
|
// Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs won't change
|
|
// run by run. This simplifies checking what utxos are included in the built txs.
|
|
const utxosOffset uint64 = 2025
|
|
|
|
return []*lux.UTXO{ // currently, the wallet scans UTXOs in the order provided here
|
|
{ // a small UTXO first, which should not be enough to pay fees
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset),
|
|
OutputIndex: uint32(utxosOffset),
|
|
},
|
|
Asset: lux.Asset{ID: utxoAssetID},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: 2 * constants.MilliLux,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 0,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
Threshold: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset + 2),
|
|
OutputIndex: uint32(utxosOffset + 2),
|
|
},
|
|
Asset: lux.Asset{ID: nftAssetID},
|
|
Out: &nftfx.MintOutput{
|
|
GroupID: 1,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset + 3),
|
|
OutputIndex: uint32(utxosOffset + 3),
|
|
},
|
|
Asset: lux.Asset{ID: nftAssetID},
|
|
Out: &secp256k1fx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset + 4),
|
|
OutputIndex: uint32(utxosOffset + 4),
|
|
},
|
|
Asset: lux.Asset{ID: propertyAssetID},
|
|
Out: &propertyfx.MintOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 0,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
Threshold: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset + 5),
|
|
OutputIndex: uint32(utxosOffset + 5),
|
|
},
|
|
Asset: lux.Asset{ID: propertyAssetID},
|
|
Out: &propertyfx.OwnedOutput{
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 0,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
Threshold: 1,
|
|
},
|
|
},
|
|
},
|
|
{ // a large UTXO last, which should be enough to pay any fee by itself
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.Empty.Prefix(utxosOffset + 6),
|
|
OutputIndex: uint32(utxosOffset + 6),
|
|
},
|
|
Asset: lux.Asset{ID: utxoAssetID},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: 9 * constants.Lux,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 0,
|
|
Addrs: []ids.ShortID{utxosKey.PublicKey().Address()},
|
|
Threshold: 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|