diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index abc4f141ef3..ef6deedfe84 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -2,7 +2,7 @@ name: Run fuzz tests on: schedule: - - cron: "0 0 * * *" + - cron: "0 0 * * *" # Once a day at midnight UTC permissions: contents: read @@ -13,8 +13,6 @@ jobs: steps: - name: Git checkout uses: actions/checkout@v4 - with: - ref: 'dev' - name: Set up Go uses: ./.github/actions/setup-go-for-project - name: Run fuzz tests diff --git a/.github/workflows/fuzz_merkledb.yml b/.github/workflows/fuzz_merkledb.yml index 1b331f0cfad..8019b449068 100644 --- a/.github/workflows/fuzz_merkledb.yml +++ b/.github/workflows/fuzz_merkledb.yml @@ -15,8 +15,6 @@ jobs: steps: - name: Git checkout uses: actions/checkout@v4 - with: - ref: 'dev' - name: Set up Go uses: ./.github/actions/setup-go-for-project - name: Run merkledb fuzz tests diff --git a/genesis/config.go b/genesis/config.go index de7d88f2f69..4ecd5f53b12 100644 --- a/genesis/config.go +++ b/genesis/config.go @@ -12,6 +12,7 @@ import ( "fmt" "os" "path/filepath" + "time" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" @@ -21,6 +22,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/signer" ) +const localNetworkUpdateStartTimePeriod = 9 * 30 * 24 * time.Hour // 9 months + var ( _ utils.Sortable[Allocation] = Allocation{} @@ -170,6 +173,10 @@ var ( // LocalConfig is the config that should be used to generate a local // genesis. LocalConfig Config + + // unmodifiedLocalConfig is the LocalConfig before advancing the StartTime + // to a recent value. + unmodifiedLocalConfig Config ) func init() { @@ -196,10 +203,21 @@ func init() { panic(err) } - LocalConfig, err = unparsedLocalConfig.Parse() + unmodifiedLocalConfig, err = unparsedLocalConfig.Parse() if err != nil { panic(err) } + + // Renew the staking start time of the local config if required + definedStartTime := time.Unix(int64(unmodifiedLocalConfig.StartTime), 0) + recentStartTime := getRecentStartTime( + definedStartTime, + time.Now(), + localNetworkUpdateStartTimePeriod, + ) + + LocalConfig = unmodifiedLocalConfig + LocalConfig.StartTime = uint64(recentStartTime.Unix()) } func GetConfig(networkID uint32) *Config { @@ -247,3 +265,21 @@ func parseGenesisJSONBytesToConfig(bytes []byte) (*Config, error) { } return &config, nil } + +// getRecentStartTime advances [definedStartTime] in chunks of [period]. It +// returns the latest startTime that isn't after [now]. +func getRecentStartTime( + definedStartTime time.Time, + now time.Time, + period time.Duration, +) time.Time { + startTime := definedStartTime + for { + nextStartTime := startTime.Add(period) + if now.Before(nextStartTime) { + break + } + startTime = nextStartTime + } + return startTime +} diff --git a/genesis/config_test.go b/genesis/config_test.go index 8a9bc96a9c9..693ee267d95 100644 --- a/genesis/config_test.go +++ b/genesis/config_test.go @@ -5,6 +5,7 @@ package genesis import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -51,3 +52,68 @@ func TestAllocationCompare(t *testing.T) { }) } } + +func TestGetRecentStartTime(t *testing.T) { + type test struct { + name string + defined time.Time + now time.Time + expected time.Time + } + tests := []test{ + { + name: "before 1 period and 1 second", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(-localNetworkUpdateStartTimePeriod - time.Second), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + }, + { + name: "before 1 second", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(-time.Second), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + }, + { + name: "equal", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + }, + { + name: "after 1 second", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(time.Second), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + }, + { + name: "after 1 period", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(localNetworkUpdateStartTimePeriod), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(localNetworkUpdateStartTimePeriod), + }, + { + name: "after 1 period and 1 second", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(localNetworkUpdateStartTimePeriod + time.Second), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(localNetworkUpdateStartTimePeriod), + }, + { + name: "after 2 periods", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(2 * localNetworkUpdateStartTimePeriod), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(2 * localNetworkUpdateStartTimePeriod), + }, + { + name: "after 2 periods and 1 second", + defined: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC), + now: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(2*localNetworkUpdateStartTimePeriod + time.Second), + expected: time.Date(2024, time.July, 15, 4, 0, 0, 0, time.UTC).Add(2 * localNetworkUpdateStartTimePeriod), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := getRecentStartTime(tt.defined, tt.now, localNetworkUpdateStartTimePeriod) + require.Equal(t, tt.expected, actual) + }) + } +} diff --git a/genesis/genesis_local.json b/genesis/genesis_local.json index be8dba4edbf..0137de9dd1b 100644 --- a/genesis/genesis_local.json +++ b/genesis/genesis_local.json @@ -38,7 +38,7 @@ ] } ], - "startTime": 1690862400, + "startTime": 1721016000, "initialStakeDuration": 31536000, "initialStakeDurationOffset": 5400, "initialStakedFunds": [ @@ -93,4 +93,4 @@ ], "cChainGenesis": "{\"config\":{\"chainId\":43112,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC\":{\"balance\":\"0x295BE96E64066972000000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}", "message": "{{ fun_quote }}" -} +} \ No newline at end of file diff --git a/genesis/genesis_test.go b/genesis/genesis_test.go index 679fc05be91..c2ab1e3e147 100644 --- a/genesis/genesis_test.go +++ b/genesis/genesis_test.go @@ -343,28 +343,27 @@ func TestGenesisFromFlag(t *testing.T) { func TestGenesis(t *testing.T) { tests := []struct { - networkID uint32 + config *Config expectedID string }{ { - networkID: constants.MainnetID, + config: &MainnetConfig, expectedID: "UUvXi6j7QhVvgpbKM89MP5HdrxKm9CaJeHc187TsDNf8nZdLk", }, { - networkID: constants.FujiID, + config: &FujiConfig, expectedID: "MSj6o9TpezwsQx4Tv7SHqpVvCbJ8of1ikjsqPZ1bKRjc9zBy3", }, { - networkID: constants.LocalID, - expectedID: "S4BvHv1XyihF9gXkJKXWWwQuuDWZqesRXz6wnqavQ9FrjGfAa", + config: &unmodifiedLocalConfig, + expectedID: "23DnViuN2kgePiBN4JxZXh1VrfXca2rwUp6XrKgNGdj3TSQjiN", }, } for _, test := range tests { - t.Run(constants.NetworkIDToNetworkName[test.networkID], func(t *testing.T) { + t.Run(constants.NetworkIDToNetworkName[test.config.NetworkID], func(t *testing.T) { require := require.New(t) - config := GetConfig(test.networkID) - genesisBytes, _, err := FromConfig(config) + genesisBytes, _, err := FromConfig(test.config) require.NoError(err) var genesisID ids.ID = hashing.ComputeHash256Array(genesisBytes) diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index b6005a8f156..caa04010353 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -1202,6 +1202,60 @@ Testnet: U8iRqJoiJm8xZHAacmvYyZVwqQx6uDNtQeP3CQ6fcgQk3JqnK ::: +### `platform.getSubnet` + +Get owners and elastic info about the Subnet. + +**Signature:** + +```sh +platform.getSubnet({ + subnetID: string +}) -> +{ + isPermissioned: bool, + controlKeys: []string, + threshold: string, + locktime: string, + subnetTransformationTxID: string +} +``` + +- `subnetID` is the ID of the Subnet to get information about. If omitted, fails. +- `threshold` signatures from addresses in `controlKeys` are needed to make changes to + a permissioned subnet. If the Subnet is a PoS Subnet, then `threshold` will be `0` and `controlKeys` + will be empty. +- changes can not be made into the subnet until `locktime` is in the past. +- `subnetTransformationTxID` is the ID of the transaction that changed the subnet into a elastic one, + for when this change was performed. + +**Example Call:** + +```sh +curl -X POST --data '{ + "jsonrpc": "2.0", + "method": "platform.getSubnet", + "params": {"subnetID":"Vz2ArUpigHt7fyE79uF3gAXvTPLJi2LGgZoMpgNPHowUZJxBb"}, + "id": 1 +}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/P +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "isPermissioned": true, + "controlKeys": ["P-fuji1ztvstx6naeg6aarfd047fzppdt8v4gsah88e0c","P-fuji193kvt4grqewv6ce2x59wnhydr88xwdgfcedyr3"], + "threshold": "1", + "locktime": "0", + "subnetTransformationTxID": "11111111111111111111111111111111LpoYY" + }, + "id": 1 +} +``` + ### `platform.getSubnets` :::caution diff --git a/vms/platformvm/warp/README.md b/vms/platformvm/warp/README.md index a21861bc724..4365a08bc0b 100644 --- a/vms/platformvm/warp/README.md +++ b/vms/platformvm/warp/README.md @@ -88,7 +88,7 @@ Once the validator set of a blockchain is willing to sign an arbitrary message ` ## Verifying / Receiving an Avalanche Warp Message -Avalanache Warp Messages are verified within the context of a specific P-Chain height included in the [ProposerVM](../../proposervm/README.md)'s header. The P-Chain height is provided as context to the underlying VM when verifying the underlying VM's blocks (implemented by the optional interface [WithVerifyContext](../../../snow/engine/snowman/block/block_context_vm.go)). +Avalanche Warp Messages are verified within the context of a specific P-Chain height included in the [ProposerVM](../../proposervm/README.md)'s header. The P-Chain height is provided as context to the underlying VM when verifying the underlying VM's blocks (implemented by the optional interface [WithVerifyContext](../../../snow/engine/snowman/block/block_context_vm.go)). To verify the message, the underlying VM utilizes this `warp` package to perform the following steps: diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index 9f73b9e399b..80164f05294 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -67,7 +67,7 @@ func TestBaseTx(t *testing.T) { builder = builder.New(set.Of(utxoAddr), testContext, backend) // data to build the transaction - outputsToMove = []*avax.TransferableOutput{{ + outputToMove = &avax.TransferableOutput{ Asset: avax.Asset{ID: avaxAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: 7 * units.Avax, @@ -76,22 +76,25 @@ func TestBaseTx(t *testing.T) { Addrs: []ids.ShortID{utxoAddr}, }, }, - }} + } ) - utx, err := builder.NewBaseTx(outputsToMove) + utx, err := builder.NewBaseTx([]*avax.TransferableOutput{outputToMove}) require.NoError(err) - // check UTXOs selection and fee financing - ins := utx.Ins - outs := utx.Outs - require.Len(ins, 2) - require.Len(outs, 2) + // check that the output is included in the transaction + require.Contains(utx.Outs, outputToMove) - expectedConsumed := testContext.BaseTxFee + outputsToMove[0].Out.Amount() - consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) - require.Equal(outputsToMove[0], outs[1]) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.BaseTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestAddSubnetValidatorTx(t *testing.T) { @@ -140,15 +143,16 @@ func TestAddSubnetValidatorTx(t *testing.T) { utx, err := builder.NewAddSubnetValidatorTx(subnetValidator) 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.AddSubnetValidatorFee - consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.AddSubnetValidatorFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestRemoveSubnetValidatorTx(t *testing.T) { @@ -191,15 +195,16 @@ func TestRemoveSubnetValidatorTx(t *testing.T) { ) 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) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.BaseTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestCreateChainTx(t *testing.T) { @@ -250,15 +255,16 @@ func TestCreateChainTx(t *testing.T) { ) 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.CreateBlockchainTxFee - consumed := ins[0].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.CreateBlockchainTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestCreateSubnetTx(t *testing.T) { @@ -298,15 +304,16 @@ func TestCreateSubnetTx(t *testing.T) { utx, err := builder.NewCreateSubnetTx(subnetOwner) 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.CreateSubnetTxFee - consumed := ins[0].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.CreateSubnetTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestTransferSubnetOwnershipTx(t *testing.T) { @@ -349,15 +356,16 @@ func TestTransferSubnetOwnershipTx(t *testing.T) { ) 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) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.BaseTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestImportTx(t *testing.T) { @@ -397,17 +405,18 @@ func TestImportTx(t *testing.T) { ) require.NoError(err) - // check UTXOs selection and fee financing - ins := utx.Ins - outs := utx.Outs - importedIns := utx.ImportedInputs - require.Empty(ins) // we spend the imported input (at least partially) - require.Len(importedIns, 1) - require.Len(outs, 1) - - expectedConsumed := testContext.BaseTxFee - consumed := importedIns[0].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + require.Empty(utx.Ins) // we spend the imported input (at least partially) + + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.BaseTxFee, + }, + ), + addInputAmounts(utx.Ins, utx.ImportedInputs), + ) } func TestExportTx(t *testing.T) { @@ -447,16 +456,18 @@ func TestExportTx(t *testing.T) { ) 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.ExportedOutputs, exportedOutputs) + + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs, utx.ExportedOutputs), + map[ids.ID]uint64{ + avaxAssetID: testContext.BaseTxFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestTransformSubnetTx(t *testing.T) { @@ -515,18 +526,17 @@ func TestTransformSubnetTx(t *testing.T) { ) require.NoError(err) - // check UTXOs selection and fee financing - ins := utx.Ins - outs := utx.Outs - require.Len(ins, 2) - require.Len(outs, 2) - - expectedConsumedSubnetAsset := maxSupply - initialSupply - consumedSubnetAsset := ins[0].In.Amount() - outs[1].Out.Amount() - require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) - expectedConsumed := testContext.TransformSubnetTxFee - consumed := ins[1].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs), + map[ids.ID]uint64{ + avaxAssetID: testContext.TransformSubnetTxFee, + subnetAssetID: maxSupply - initialSupply, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestAddPermissionlessValidatorTx(t *testing.T) { @@ -583,20 +593,24 @@ func TestAddPermissionlessValidatorTx(t *testing.T) { ) require.NoError(err) - // check UTXOs selection and fee financing - ins := utx.Ins - staked := utx.StakeOuts - outs := utx.Outs - require.Len(ins, 4) - require.Len(staked, 2) - require.Len(outs, 2) - - expectedConsumedSubnetAsset := utx.Validator.Weight() - consumedSubnetAsset := staked[0].Out.Amount() + staked[1].Out.Amount() - require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) - expectedConsumed := testContext.AddPrimaryNetworkValidatorFee - consumed := ins[1].In.Amount() + ins[3].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check stake amount + require.Equal( + map[ids.ID]uint64{ + avaxAssetID: 2 * units.Avax, + }, + addOutputAmounts(utx.StakeOuts), + ) + + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs, utx.StakeOuts), + map[ids.ID]uint64{ + avaxAssetID: testContext.AddPrimaryNetworkValidatorFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func TestAddPermissionlessDelegatorTx(t *testing.T) { @@ -641,20 +655,24 @@ func TestAddPermissionlessDelegatorTx(t *testing.T) { ) require.NoError(err) - // check UTXOs selection and fee financing - ins := utx.Ins - staked := utx.StakeOuts - outs := utx.Outs - require.Len(ins, 4) - require.Len(staked, 2) - require.Len(outs, 2) - - expectedConsumedSubnetAsset := utx.Validator.Weight() - consumedSubnetAsset := staked[0].Out.Amount() + staked[1].Out.Amount() - require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) - expectedConsumed := testContext.AddPrimaryNetworkDelegatorFee - consumed := ins[1].In.Amount() + ins[3].In.Amount() - outs[0].Out.Amount() - require.Equal(expectedConsumed, consumed) + // check stake amount + require.Equal( + map[ids.ID]uint64{ + avaxAssetID: 2 * units.Avax, + }, + addOutputAmounts(utx.StakeOuts), + ) + + // check fee calculation + require.Equal( + addAmounts( + addOutputAmounts(utx.Outs, utx.StakeOuts), + map[ids.ID]uint64{ + avaxAssetID: testContext.AddPrimaryNetworkDelegatorFee, + }, + ), + addInputAmounts(utx.Ins), + ) } func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { @@ -745,3 +763,33 @@ func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { }, } } + +func addAmounts(allAmounts ...map[ids.ID]uint64) map[ids.ID]uint64 { + amounts := make(map[ids.ID]uint64) + for _, amountsToAdd := range allAmounts { + for assetID, amount := range amountsToAdd { + amounts[assetID] += amount + } + } + return amounts +} + +func addInputAmounts(inputSlices ...[]*avax.TransferableInput) map[ids.ID]uint64 { + consumed := make(map[ids.ID]uint64) + for _, inputs := range inputSlices { + for _, in := range inputs { + consumed[in.AssetID()] += in.In.Amount() + } + } + return consumed +} + +func addOutputAmounts(outputSlices ...[]*avax.TransferableOutput) map[ids.ID]uint64 { + produced := make(map[ids.ID]uint64) + for _, outputs := range outputSlices { + for _, out := range outputs { + produced[out.AssetID()] += out.Out.Amount() + } + } + return produced +}