Skip to content
Snippets Groups Projects
outputs.go 4.59 KiB
package transaction

import (
	"bytes"
	"github.com/iotaledger/hive.go/marshalutil"
	"sort"

	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
	"github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap"

	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
)

// Outputs represents a list of Outputs that are part of a transaction.
type Outputs struct {
	*orderedmap.OrderedMap
}

// NewOutputs is the constructor of the Outputs struct and creates the list of Outputs from the given details.
func NewOutputs(outputs map[address.Address][]*balance.Balance) (result *Outputs) {
	// sorting outputs first before adding to the ordered map to have a deterministic order
	toSort := make([]address.Address, 0, len(outputs))
	for a := range outputs {
		toSort = append(toSort, a)
	}

	sort.Slice(toSort, func(i, j int) bool {
		return bytes.Compare(toSort[i][:], toSort[j][:]) < 0
	})

	result = &Outputs{orderedmap.New()}
	for _, addr := range toSort {
		result.Add(addr, outputs[addr])
	}
	return
}

// OutputsFromBytes reads the bytes and unmarshals the given information into an *Outputs object. It either creates a
// new object, or uses the optional object provided in the arguments.
func OutputsFromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *Outputs, consumedBytes int, err error) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Outputs{orderedmap.New()}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to OutputFromBytes")
	}

	// initialize helper
	marshalUtil := marshalutil.New(bytes)

	// read number of addresses in the outputs
	addressCount, err := marshalUtil.ReadUint32()
	if err != nil {
		return
	}

	// iterate the corresponding times and collect addresses + their details
	for i := uint32(0); i < addressCount; i++ {
		// read address
		addr, addressErr := address.Parse(marshalUtil)
		if addressErr != nil {
			err = addressErr

			return
		}

		// read number of balances in the outputs
		balanceCount, balanceCountErr := marshalUtil.ReadUint32()
		if balanceCountErr != nil {
			err = balanceCountErr

			return
		}

		// iterate the corresponding times and collect balances
		coloredBalances := make([]*balance.Balance, balanceCount)
		for j := uint32(0); j < balanceCount; j++ {
			coloredBalance, coloredBalanceErr := marshalUtil.Parse(func(data []byte) (interface{}, int, error) { return balance.FromBytes(data) })
			if coloredBalanceErr != nil {
				err = coloredBalanceErr

				return
			}

			coloredBalances[j] = coloredBalance.(*balance.Balance)
		}

		// add the gathered information as an output
		result.Add(addr, coloredBalances)
	}

	// return the number of bytes we processed
	consumedBytes = marshalUtil.ReadOffset()

	return
}

// Add adds a new Output to the list of Outputs.
func (outputs *Outputs) Add(address address.Address, balances []*balance.Balance) *Outputs {
	outputs.Set(address, balances)

	return outputs
}

// ForEach iterates through the Outputs and calls them consumer for every found one. The iteration can be aborted by
// returning false in the consumer.
func (outputs *Outputs) ForEach(consumer func(address address.Address, balances []*balance.Balance) bool) bool {
	return outputs.OrderedMap.ForEach(func(key, value interface{}) bool {
		return consumer(key.(address.Address), value.([]*balance.Balance))
	})
}

// Bytes returns a marshaled version of this list of Outputs.
func (outputs *Outputs) Bytes() []byte {
	marshalUtil := marshalutil.New()

	if outputs == nil {
		marshalUtil.WriteUint32(0)

		return marshalUtil.Bytes()
	}

	marshalUtil.WriteUint32(uint32(outputs.Size()))
	outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
		marshalUtil.WriteBytes(address.Bytes())
		marshalUtil.WriteUint32(uint32(len(balances)))

		for _, bal := range balances {
			marshalUtil.WriteBytes(bal.Bytes())
		}

		return true
	})

	return marshalUtil.Bytes()
}
// String returns a human readable version of this list of Outputs (for debug purposes).
func (outputs *Outputs) String() string {
	if outputs == nil {
		return "<nil>"
	}

	result := "[\n"
	empty := true
	outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
		empty = false

		result += "    " + address.String() + ": [\n"

		balancesEmpty := true
		for _, bal := range balances {
			balancesEmpty = false

			result += "        " + bal.String() + ",\n"
		}

		if balancesEmpty {
			result += "        <empty>\n"
		}

		result += "    ]\n"

		return true
	})

	if empty {
		result += "    <empty>\n"
	}

	return result + "]"
}