github.com/dim4egster/coreth@v0.10.2/plugin/evm/export_tx.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"math/big"
    10  
    11  	"github.com/dim4egster/coreth/core/state"
    12  	"github.com/dim4egster/coreth/params"
    13  
    14  	"github.com/dim4egster/qmallgo/chains/atomic"
    15  	"github.com/dim4egster/qmallgo/ids"
    16  	"github.com/dim4egster/qmallgo/snow"
    17  	"github.com/dim4egster/qmallgo/utils/constants"
    18  	"github.com/dim4egster/qmallgo/utils/crypto"
    19  	"github.com/dim4egster/qmallgo/utils/math"
    20  	"github.com/dim4egster/qmallgo/utils/wrappers"
    21  	"github.com/dim4egster/qmallgo/vms/components/avax"
    22  	"github.com/dim4egster/qmallgo/vms/components/verify"
    23  	"github.com/dim4egster/qmallgo/vms/secp256k1fx"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/log"
    26  )
    27  
    28  var (
    29  	_                           UnsignedAtomicTx       = &UnsignedExportTx{}
    30  	_                           secp256k1fx.UnsignedTx = &UnsignedExportTx{}
    31  	errExportNonAVAXInputBanff                         = errors.New("export input cannot contain non-AVAX in Banff")
    32  	errExportNonAVAXOutputBanff                        = errors.New("export output cannot contain non-AVAX in Banff")
    33  )
    34  
    35  // UnsignedExportTx is an unsigned ExportTx
    36  type UnsignedExportTx struct {
    37  	avax.Metadata
    38  	// ID of the network on which this tx was issued
    39  	NetworkID uint32 `serialize:"true" json:"networkID"`
    40  	// ID of this blockchain.
    41  	BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
    42  	// Which chain to send the funds to
    43  	DestinationChain ids.ID `serialize:"true" json:"destinationChain"`
    44  	// Inputs
    45  	Ins []EVMInput `serialize:"true" json:"inputs"`
    46  	// Outputs that are exported to the chain
    47  	ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"`
    48  }
    49  
    50  // InputUTXOs returns a set of all the hash(address:nonce) exporting funds.
    51  func (utx *UnsignedExportTx) InputUTXOs() ids.Set {
    52  	set := ids.NewSet(len(utx.Ins))
    53  	for _, in := range utx.Ins {
    54  		// Total populated bytes is exactly 32 bytes.
    55  		// 8 (Nonce) + 4 (Address Length) + 20 (Address)
    56  		var rawID [32]byte
    57  		packer := wrappers.Packer{Bytes: rawID[:]}
    58  		packer.PackLong(in.Nonce)
    59  		packer.PackBytes(in.Address.Bytes())
    60  		set.Add(ids.ID(rawID))
    61  	}
    62  	return set
    63  }
    64  
    65  // Verify this transaction is well-formed
    66  func (utx *UnsignedExportTx) Verify(
    67  	ctx *snow.Context,
    68  	rules params.Rules,
    69  ) error {
    70  	switch {
    71  	case utx == nil:
    72  		return errNilTx
    73  	case len(utx.ExportedOutputs) == 0:
    74  		return errNoExportOutputs
    75  	case utx.NetworkID != ctx.NetworkID:
    76  		return errWrongNetworkID
    77  	case ctx.ChainID != utx.BlockchainID:
    78  		return errWrongBlockchainID
    79  	}
    80  
    81  	// Make sure that the tx has a valid peer chain ID
    82  	if rules.IsApricotPhase5 {
    83  		// Note that SameSubnet verifies that [tx.DestinationChain] isn't this
    84  		// chain's ID
    85  		if err := verify.SameSubnet(ctx, utx.DestinationChain); err != nil {
    86  			return errWrongChainID
    87  		}
    88  	} else {
    89  		if utx.DestinationChain != ctx.XChainID {
    90  			return errWrongChainID
    91  		}
    92  	}
    93  
    94  	for _, in := range utx.Ins {
    95  		if err := in.Verify(); err != nil {
    96  			return err
    97  		}
    98  		if rules.IsBanff && in.AssetID != ctx.AVAXAssetID {
    99  			return errExportNonAVAXInputBanff
   100  		}
   101  	}
   102  
   103  	for _, out := range utx.ExportedOutputs {
   104  		if err := out.Verify(); err != nil {
   105  			return err
   106  		}
   107  		assetID := out.AssetID()
   108  		if assetID != ctx.AVAXAssetID && utx.DestinationChain == constants.PlatformChainID {
   109  			return errWrongChainID
   110  		}
   111  		if rules.IsBanff && assetID != ctx.AVAXAssetID {
   112  			return errExportNonAVAXOutputBanff
   113  		}
   114  	}
   115  	if !avax.IsSortedTransferableOutputs(utx.ExportedOutputs, Codec) {
   116  		return errOutputsNotSorted
   117  	}
   118  	if rules.IsApricotPhase1 && !IsSortedAndUniqueEVMInputs(utx.Ins) {
   119  		return errInputsNotSortedUnique
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  func (utx *UnsignedExportTx) GasUsed(fixedFee bool) (uint64, error) {
   126  	byteCost := calcBytesCost(len(utx.Bytes()))
   127  	numSigs := uint64(len(utx.Ins))
   128  	sigCost, err := math.Mul64(numSigs, secp256k1fx.CostPerSignature)
   129  	if err != nil {
   130  		return 0, err
   131  	}
   132  	cost, err := math.Add64(byteCost, sigCost)
   133  	if err != nil {
   134  		return 0, err
   135  	}
   136  	if fixedFee {
   137  		cost, err = math.Add64(cost, params.AtomicTxBaseCost)
   138  		if err != nil {
   139  			return 0, err
   140  		}
   141  	}
   142  
   143  	return cost, nil
   144  }
   145  
   146  // Amount of [assetID] burned by this transaction
   147  func (utx *UnsignedExportTx) Burned(assetID ids.ID) (uint64, error) {
   148  	var (
   149  		spent uint64
   150  		input uint64
   151  		err   error
   152  	)
   153  	for _, out := range utx.ExportedOutputs {
   154  		if out.AssetID() == assetID {
   155  			spent, err = math.Add64(spent, out.Output().Amount())
   156  			if err != nil {
   157  				return 0, err
   158  			}
   159  		}
   160  	}
   161  	for _, in := range utx.Ins {
   162  		if in.AssetID == assetID {
   163  			input, err = math.Add64(input, in.Amount)
   164  			if err != nil {
   165  				return 0, err
   166  			}
   167  		}
   168  	}
   169  
   170  	return math.Sub64(input, spent)
   171  }
   172  
   173  // SemanticVerify this transaction is valid.
   174  func (utx *UnsignedExportTx) SemanticVerify(
   175  	vm *VM,
   176  	stx *Tx,
   177  	_ *Block,
   178  	baseFee *big.Int,
   179  	rules params.Rules,
   180  ) error {
   181  	if err := utx.Verify(vm.ctx, rules); err != nil {
   182  		return err
   183  	}
   184  
   185  	// Check the transaction consumes and produces the right amounts
   186  	fc := avax.NewFlowChecker()
   187  	switch {
   188  	// Apply dynamic fees to export transactions as of Apricot Phase 3
   189  	case rules.IsApricotPhase3:
   190  		gasUsed, err := stx.GasUsed(rules.IsApricotPhase5)
   191  		if err != nil {
   192  			return err
   193  		}
   194  		txFee, err := calculateDynamicFee(gasUsed, baseFee)
   195  		if err != nil {
   196  			return err
   197  		}
   198  		fc.Produce(vm.ctx.AVAXAssetID, txFee)
   199  	// Apply fees to export transactions before Apricot Phase 3
   200  	default:
   201  		fc.Produce(vm.ctx.AVAXAssetID, params.AvalancheAtomicTxFee)
   202  	}
   203  	for _, out := range utx.ExportedOutputs {
   204  		fc.Produce(out.AssetID(), out.Output().Amount())
   205  	}
   206  	for _, in := range utx.Ins {
   207  		fc.Consume(in.AssetID, in.Amount)
   208  	}
   209  
   210  	if err := fc.Verify(); err != nil {
   211  		return fmt.Errorf("export tx flow check failed due to: %w", err)
   212  	}
   213  
   214  	if len(utx.Ins) != len(stx.Creds) {
   215  		return fmt.Errorf("export tx contained mismatched number of inputs/credentials (%d vs. %d)", len(utx.Ins), len(stx.Creds))
   216  	}
   217  
   218  	for i, input := range utx.Ins {
   219  		cred, ok := stx.Creds[i].(*secp256k1fx.Credential)
   220  		if !ok {
   221  			return fmt.Errorf("expected *secp256k1fx.Credential but got %T", cred)
   222  		}
   223  		if err := cred.Verify(); err != nil {
   224  			return err
   225  		}
   226  
   227  		if len(cred.Sigs) != 1 {
   228  			return fmt.Errorf("expected one signature for EVM Input Credential, but found: %d", len(cred.Sigs))
   229  		}
   230  		pubKeyIntf, err := vm.secpFactory.RecoverPublicKey(utx.Bytes(), cred.Sigs[0][:])
   231  		if err != nil {
   232  			return err
   233  		}
   234  		pubKey, ok := pubKeyIntf.(*crypto.PublicKeySECP256K1R)
   235  		if !ok {
   236  			// This should never happen
   237  			return fmt.Errorf("expected *crypto.PublicKeySECP256K1R but got %T", pubKeyIntf)
   238  		}
   239  		if input.Address != PublicKeyToEthAddress(pubKey) {
   240  			return errPublicKeySignatureMismatch
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // AtomicOps returns the atomic operations for this transaction.
   248  func (utx *UnsignedExportTx) AtomicOps() (ids.ID, *atomic.Requests, error) {
   249  	txID := utx.ID()
   250  
   251  	elems := make([]*atomic.Element, len(utx.ExportedOutputs))
   252  	for i, out := range utx.ExportedOutputs {
   253  		utxo := &avax.UTXO{
   254  			UTXOID: avax.UTXOID{
   255  				TxID:        txID,
   256  				OutputIndex: uint32(i),
   257  			},
   258  			Asset: avax.Asset{ID: out.AssetID()},
   259  			Out:   out.Out,
   260  		}
   261  
   262  		utxoBytes, err := Codec.Marshal(codecVersion, utxo)
   263  		if err != nil {
   264  			return ids.ID{}, nil, err
   265  		}
   266  		utxoID := utxo.InputID()
   267  		elem := &atomic.Element{
   268  			Key:   utxoID[:],
   269  			Value: utxoBytes,
   270  		}
   271  		if out, ok := utxo.Out.(avax.Addressable); ok {
   272  			elem.Traits = out.Addresses()
   273  		}
   274  
   275  		elems[i] = elem
   276  	}
   277  
   278  	return utx.DestinationChain, &atomic.Requests{PutRequests: elems}, nil
   279  }
   280  
   281  // newExportTx returns a new ExportTx
   282  func (vm *VM) newExportTx(
   283  	assetID ids.ID, // AssetID of the tokens to export
   284  	amount uint64, // Amount of tokens to export
   285  	chainID ids.ID, // Chain to send the UTXOs to
   286  	to ids.ShortID, // Address of chain recipient
   287  	baseFee *big.Int, // fee to use post-AP3
   288  	keys []*crypto.PrivateKeySECP256K1R, // Pay the fee and provide the tokens
   289  ) (*Tx, error) {
   290  	outs := []*avax.TransferableOutput{{ // Exported to X-Chain
   291  		Asset: avax.Asset{ID: assetID},
   292  		Out: &secp256k1fx.TransferOutput{
   293  			Amt: amount,
   294  			OutputOwners: secp256k1fx.OutputOwners{
   295  				Locktime:  0,
   296  				Threshold: 1,
   297  				Addrs:     []ids.ShortID{to},
   298  			},
   299  		},
   300  	}}
   301  
   302  	var (
   303  		avaxNeeded           uint64 = 0
   304  		ins, avaxIns         []EVMInput
   305  		signers, avaxSigners [][]*crypto.PrivateKeySECP256K1R
   306  		err                  error
   307  	)
   308  
   309  	// consume non-AVAX
   310  	if assetID != vm.ctx.AVAXAssetID {
   311  		ins, signers, err = vm.GetSpendableFunds(keys, assetID, amount)
   312  		if err != nil {
   313  			return nil, fmt.Errorf("couldn't generate tx inputs/signers: %w", err)
   314  		}
   315  	} else {
   316  		avaxNeeded = amount
   317  	}
   318  
   319  	rules := vm.currentRules()
   320  	switch {
   321  	case rules.IsApricotPhase3:
   322  		utx := &UnsignedExportTx{
   323  			NetworkID:        vm.ctx.NetworkID,
   324  			BlockchainID:     vm.ctx.ChainID,
   325  			DestinationChain: chainID,
   326  			Ins:              ins,
   327  			ExportedOutputs:  outs,
   328  		}
   329  		tx := &Tx{UnsignedAtomicTx: utx}
   330  		if err := tx.Sign(vm.codec, nil); err != nil {
   331  			return nil, err
   332  		}
   333  
   334  		var cost uint64
   335  		cost, err = tx.GasUsed(rules.IsApricotPhase5)
   336  		if err != nil {
   337  			return nil, err
   338  		}
   339  
   340  		avaxIns, avaxSigners, err = vm.GetSpendableAVAXWithFee(keys, avaxNeeded, cost, baseFee)
   341  	default:
   342  		var newAvaxNeeded uint64
   343  		newAvaxNeeded, err = math.Add64(avaxNeeded, params.AvalancheAtomicTxFee)
   344  		if err != nil {
   345  			return nil, errOverflowExport
   346  		}
   347  		avaxIns, avaxSigners, err = vm.GetSpendableFunds(keys, vm.ctx.AVAXAssetID, newAvaxNeeded)
   348  	}
   349  	if err != nil {
   350  		return nil, fmt.Errorf("couldn't generate tx inputs/signers: %w", err)
   351  	}
   352  	ins = append(ins, avaxIns...)
   353  	signers = append(signers, avaxSigners...)
   354  
   355  	avax.SortTransferableOutputs(outs, vm.codec)
   356  	SortEVMInputsAndSigners(ins, signers)
   357  
   358  	// Create the transaction
   359  	utx := &UnsignedExportTx{
   360  		NetworkID:        vm.ctx.NetworkID,
   361  		BlockchainID:     vm.ctx.ChainID,
   362  		DestinationChain: chainID,
   363  		Ins:              ins,
   364  		ExportedOutputs:  outs,
   365  	}
   366  	tx := &Tx{UnsignedAtomicTx: utx}
   367  	if err := tx.Sign(vm.codec, signers); err != nil {
   368  		return nil, err
   369  	}
   370  	return tx, utx.Verify(vm.ctx, vm.currentRules())
   371  }
   372  
   373  // EVMStateTransfer executes the state update from the atomic export transaction
   374  func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error {
   375  	addrs := map[[20]byte]uint64{}
   376  	for _, from := range utx.Ins {
   377  		if from.AssetID == ctx.AVAXAssetID {
   378  			log.Debug("crosschain", "dest", utx.DestinationChain, "addr", from.Address, "amount", from.Amount, "assetID", "AVAX")
   379  			// We multiply the input amount by x2cRate to convert AVAX back to the appropriate
   380  			// denomination before export.
   381  			amount := new(big.Int).Mul(
   382  				new(big.Int).SetUint64(from.Amount), x2cRate)
   383  			if state.GetBalance(from.Address).Cmp(amount) < 0 {
   384  				return errInsufficientFunds
   385  			}
   386  			state.SubBalance(from.Address, amount)
   387  		} else {
   388  			log.Debug("crosschain", "dest", utx.DestinationChain, "addr", from.Address, "amount", from.Amount, "assetID", from.AssetID)
   389  			amount := new(big.Int).SetUint64(from.Amount)
   390  			if state.GetBalanceMultiCoin(from.Address, common.Hash(from.AssetID)).Cmp(amount) < 0 {
   391  				return errInsufficientFunds
   392  			}
   393  			state.SubBalanceMultiCoin(from.Address, common.Hash(from.AssetID), amount)
   394  		}
   395  		if state.GetNonce(from.Address) != from.Nonce {
   396  			return errInvalidNonce
   397  		}
   398  		addrs[from.Address] = from.Nonce
   399  	}
   400  	for addr, nonce := range addrs {
   401  		state.SetNonce(addr, nonce+1)
   402  	}
   403  	return nil
   404  }