github.com/dim4egster/coreth@v0.10.2/plugin/evm/import_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/crypto"
    18  	"github.com/dim4egster/qmallgo/utils/math"
    19  	"github.com/dim4egster/qmallgo/vms/components/avax"
    20  	"github.com/dim4egster/qmallgo/vms/components/verify"
    21  	"github.com/dim4egster/qmallgo/vms/secp256k1fx"
    22  	"github.com/ethereum/go-ethereum/common"
    23  	"github.com/ethereum/go-ethereum/log"
    24  )
    25  
    26  var (
    27  	_                           UnsignedAtomicTx       = &UnsignedImportTx{}
    28  	_                           secp256k1fx.UnsignedTx = &UnsignedImportTx{}
    29  	errImportNonAVAXInputBanff                         = errors.New("import input cannot contain non-AVAX in Banff")
    30  	errImportNonAVAXOutputBanff                        = errors.New("import output cannot contain non-AVAX in Banff")
    31  )
    32  
    33  // UnsignedImportTx is an unsigned ImportTx
    34  type UnsignedImportTx struct {
    35  	avax.Metadata
    36  	// ID of the network on which this tx was issued
    37  	NetworkID uint32 `serialize:"true" json:"networkID"`
    38  	// ID of this blockchain.
    39  	BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
    40  	// Which chain to consume the funds from
    41  	SourceChain ids.ID `serialize:"true" json:"sourceChain"`
    42  	// Inputs that consume UTXOs produced on the chain
    43  	ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"`
    44  	// Outputs
    45  	Outs []EVMOutput `serialize:"true" json:"outputs"`
    46  }
    47  
    48  // InputUTXOs returns the UTXOIDs of the imported funds
    49  func (utx *UnsignedImportTx) InputUTXOs() ids.Set {
    50  	set := ids.NewSet(len(utx.ImportedInputs))
    51  	for _, in := range utx.ImportedInputs {
    52  		set.Add(in.InputID())
    53  	}
    54  	return set
    55  }
    56  
    57  // Verify this transaction is well-formed
    58  func (utx *UnsignedImportTx) Verify(
    59  	ctx *snow.Context,
    60  	rules params.Rules,
    61  ) error {
    62  	switch {
    63  	case utx == nil:
    64  		return errNilTx
    65  	case len(utx.ImportedInputs) == 0:
    66  		return errNoImportInputs
    67  	case utx.NetworkID != ctx.NetworkID:
    68  		return errWrongNetworkID
    69  	case ctx.ChainID != utx.BlockchainID:
    70  		return errWrongBlockchainID
    71  	case rules.IsApricotPhase3 && len(utx.Outs) == 0:
    72  		return errNoEVMOutputs
    73  	}
    74  
    75  	// Make sure that the tx has a valid peer chain ID
    76  	if rules.IsApricotPhase5 {
    77  		// Note that SameSubnet verifies that [tx.SourceChain] isn't this
    78  		// chain's ID
    79  		if err := verify.SameSubnet(ctx, utx.SourceChain); err != nil {
    80  			return errWrongChainID
    81  		}
    82  	} else {
    83  		if utx.SourceChain != ctx.XChainID {
    84  			return errWrongChainID
    85  		}
    86  	}
    87  
    88  	for _, out := range utx.Outs {
    89  		if err := out.Verify(); err != nil {
    90  			return fmt.Errorf("EVM Output failed verification: %w", err)
    91  		}
    92  		if rules.IsBanff && out.AssetID != ctx.AVAXAssetID {
    93  			return errImportNonAVAXOutputBanff
    94  		}
    95  	}
    96  
    97  	for _, in := range utx.ImportedInputs {
    98  		if err := in.Verify(); err != nil {
    99  			return fmt.Errorf("atomic input failed verification: %w", err)
   100  		}
   101  		if rules.IsBanff && in.AssetID() != ctx.AVAXAssetID {
   102  			return errImportNonAVAXInputBanff
   103  		}
   104  	}
   105  	if !avax.IsSortedAndUniqueTransferableInputs(utx.ImportedInputs) {
   106  		return errInputsNotSortedUnique
   107  	}
   108  
   109  	if rules.IsApricotPhase2 {
   110  		if !IsSortedAndUniqueEVMOutputs(utx.Outs) {
   111  			return errOutputsNotSortedUnique
   112  		}
   113  	} else if rules.IsApricotPhase1 {
   114  		if !IsSortedEVMOutputs(utx.Outs) {
   115  			return errOutputsNotSorted
   116  		}
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func (utx *UnsignedImportTx) GasUsed(fixedFee bool) (uint64, error) {
   123  	var (
   124  		cost = calcBytesCost(len(utx.Bytes()))
   125  		err  error
   126  	)
   127  	for _, in := range utx.ImportedInputs {
   128  		inCost, err := in.In.Cost()
   129  		if err != nil {
   130  			return 0, err
   131  		}
   132  		cost, err = math.Add64(cost, inCost)
   133  		if err != nil {
   134  			return 0, err
   135  		}
   136  	}
   137  	if fixedFee {
   138  		cost, err = math.Add64(cost, params.AtomicTxBaseCost)
   139  		if err != nil {
   140  			return 0, err
   141  		}
   142  	}
   143  	return cost, nil
   144  }
   145  
   146  // Amount of [assetID] burned by this transaction
   147  func (utx *UnsignedImportTx) Burned(assetID ids.ID) (uint64, error) {
   148  	var (
   149  		spent uint64
   150  		input uint64
   151  		err   error
   152  	)
   153  	for _, out := range utx.Outs {
   154  		if out.AssetID == assetID {
   155  			spent, err = math.Add64(spent, out.Amount)
   156  			if err != nil {
   157  				return 0, err
   158  			}
   159  		}
   160  	}
   161  	for _, in := range utx.ImportedInputs {
   162  		if in.AssetID() == assetID {
   163  			input, err = math.Add64(input, in.Input().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 *UnsignedImportTx) SemanticVerify(
   175  	vm *VM,
   176  	stx *Tx,
   177  	parent *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 import 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  
   200  	// Apply fees to import transactions as of Apricot Phase 2
   201  	case rules.IsApricotPhase2:
   202  		fc.Produce(vm.ctx.AVAXAssetID, params.AvalancheAtomicTxFee)
   203  	}
   204  	for _, out := range utx.Outs {
   205  		fc.Produce(out.AssetID, out.Amount)
   206  	}
   207  	for _, in := range utx.ImportedInputs {
   208  		fc.Consume(in.AssetID(), in.Input().Amount())
   209  	}
   210  
   211  	if err := fc.Verify(); err != nil {
   212  		return fmt.Errorf("import tx flow check failed due to: %w", err)
   213  	}
   214  
   215  	if len(stx.Creds) != len(utx.ImportedInputs) {
   216  		return fmt.Errorf("import tx contained mismatched number of inputs/credentials (%d vs. %d)", len(utx.ImportedInputs), len(stx.Creds))
   217  	}
   218  
   219  	if !vm.bootstrapped {
   220  		// Allow for force committing during bootstrapping
   221  		return nil
   222  	}
   223  
   224  	utxoIDs := make([][]byte, len(utx.ImportedInputs))
   225  	for i, in := range utx.ImportedInputs {
   226  		inputID := in.UTXOID.InputID()
   227  		utxoIDs[i] = inputID[:]
   228  	}
   229  	// allUTXOBytes is guaranteed to be the same length as utxoIDs
   230  	allUTXOBytes, err := vm.ctx.SharedMemory.Get(utx.SourceChain, utxoIDs)
   231  	if err != nil {
   232  		return fmt.Errorf("failed to fetch import UTXOs from %s due to: %w", utx.SourceChain, err)
   233  	}
   234  
   235  	for i, in := range utx.ImportedInputs {
   236  		utxoBytes := allUTXOBytes[i]
   237  
   238  		utxo := &avax.UTXO{}
   239  		if _, err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil {
   240  			return fmt.Errorf("failed to unmarshal UTXO: %w", err)
   241  		}
   242  
   243  		cred := stx.Creds[i]
   244  
   245  		utxoAssetID := utxo.AssetID()
   246  		inAssetID := in.AssetID()
   247  		if utxoAssetID != inAssetID {
   248  			return errAssetIDMismatch
   249  		}
   250  
   251  		if err := vm.fx.VerifyTransfer(utx, in.In, cred, utxo.Out); err != nil {
   252  			return fmt.Errorf("import tx transfer failed verification: %w", err)
   253  		}
   254  	}
   255  
   256  	return vm.conflicts(utx.InputUTXOs(), parent)
   257  }
   258  
   259  // AtomicOps returns imported inputs spent on this transaction
   260  // We spend imported UTXOs here rather than in semanticVerify because
   261  // we don't want to remove an imported UTXO in semanticVerify
   262  // only to have the transaction not be Accepted. This would be inconsistent.
   263  // Recall that imported UTXOs are not kept in a versionDB.
   264  func (utx *UnsignedImportTx) AtomicOps() (ids.ID, *atomic.Requests, error) {
   265  	utxoIDs := make([][]byte, len(utx.ImportedInputs))
   266  	for i, in := range utx.ImportedInputs {
   267  		inputID := in.InputID()
   268  		utxoIDs[i] = inputID[:]
   269  	}
   270  	return utx.SourceChain, &atomic.Requests{RemoveRequests: utxoIDs}, nil
   271  }
   272  
   273  // newImportTx returns a new ImportTx
   274  func (vm *VM) newImportTx(
   275  	chainID ids.ID,                      // chain to import from
   276  	to common.Address,                   // Address of recipient
   277  	baseFee *big.Int,                    // fee to use post-AP3
   278  	keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds
   279  ) (*Tx, error) {
   280  	kc := secp256k1fx.NewKeychain()
   281  	for _, key := range keys {
   282  		kc.Add(key)
   283  	}
   284  
   285  	atomicUTXOs, _, _, err := vm.GetAtomicUTXOs(chainID, kc.Addresses(), ids.ShortEmpty, ids.Empty, -1)
   286  	if err != nil {
   287  		return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err)
   288  	}
   289  
   290  	return vm.newImportTxWithUTXOs(chainID, to, baseFee, kc, atomicUTXOs)
   291  }
   292  
   293  // newImportTx returns a new ImportTx
   294  func (vm *VM) newImportTxWithUTXOs(
   295  	chainID ids.ID,           // chain to import from
   296  	to common.Address,        // Address of recipient
   297  	baseFee *big.Int,         // fee to use post-AP3
   298  	kc *secp256k1fx.Keychain, // Keychain to use for signing the atomic UTXOs
   299  	atomicUTXOs []*avax.UTXO, // UTXOs to spend
   300  ) (*Tx, error) {
   301  	importedInputs := []*avax.TransferableInput{}
   302  	signers := [][]*crypto.PrivateKeySECP256K1R{}
   303  
   304  	importedAmount := make(map[ids.ID]uint64)
   305  	now := vm.clock.Unix()
   306  	for _, utxo := range atomicUTXOs {
   307  		inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now)
   308  		if err != nil {
   309  			continue
   310  		}
   311  		input, ok := inputIntf.(avax.TransferableIn)
   312  		if !ok {
   313  			continue
   314  		}
   315  		aid := utxo.AssetID()
   316  		importedAmount[aid], err = math.Add64(importedAmount[aid], input.Amount())
   317  		if err != nil {
   318  			return nil, err
   319  		}
   320  		importedInputs = append(importedInputs, &avax.TransferableInput{
   321  			UTXOID: utxo.UTXOID,
   322  			Asset:  utxo.Asset,
   323  			In:     input,
   324  		})
   325  		signers = append(signers, utxoSigners)
   326  	}
   327  	avax.SortTransferableInputsWithSigners(importedInputs, signers)
   328  	importedAVAXAmount := importedAmount[vm.ctx.AVAXAssetID]
   329  
   330  	outs := make([]EVMOutput, 0, len(importedAmount))
   331  	// This will create unique outputs (in the context of sorting)
   332  	// since each output will have a unique assetID
   333  	for assetID, amount := range importedAmount {
   334  		// Skip the AVAX amount since it is included separately to account for
   335  		// the fee
   336  		if assetID == vm.ctx.AVAXAssetID || amount == 0 {
   337  			continue
   338  		}
   339  		outs = append(outs, EVMOutput{
   340  			Address: to,
   341  			Amount:  amount,
   342  			AssetID: assetID,
   343  		})
   344  	}
   345  
   346  	rules := vm.currentRules()
   347  
   348  	var (
   349  		txFeeWithoutChange uint64
   350  		txFeeWithChange    uint64
   351  	)
   352  	switch {
   353  	case rules.IsApricotPhase3:
   354  		if baseFee == nil {
   355  			return nil, errNilBaseFeeApricotPhase3
   356  		}
   357  		utx := &UnsignedImportTx{
   358  			NetworkID:      vm.ctx.NetworkID,
   359  			BlockchainID:   vm.ctx.ChainID,
   360  			Outs:           outs,
   361  			ImportedInputs: importedInputs,
   362  			SourceChain:    chainID,
   363  		}
   364  		tx := &Tx{UnsignedAtomicTx: utx}
   365  		if err := tx.Sign(vm.codec, nil); err != nil {
   366  			return nil, err
   367  		}
   368  
   369  		gasUsedWithoutChange, err := tx.GasUsed(rules.IsApricotPhase5)
   370  		if err != nil {
   371  			return nil, err
   372  		}
   373  		gasUsedWithChange := gasUsedWithoutChange + EVMOutputGas
   374  
   375  		txFeeWithoutChange, err = calculateDynamicFee(gasUsedWithoutChange, baseFee)
   376  		if err != nil {
   377  			return nil, err
   378  		}
   379  		txFeeWithChange, err = calculateDynamicFee(gasUsedWithChange, baseFee)
   380  		if err != nil {
   381  			return nil, err
   382  		}
   383  	case rules.IsApricotPhase2:
   384  		txFeeWithoutChange = params.AvalancheAtomicTxFee
   385  		txFeeWithChange = params.AvalancheAtomicTxFee
   386  	}
   387  
   388  	// AVAX output
   389  	if importedAVAXAmount < txFeeWithoutChange { // imported amount goes toward paying tx fee
   390  		return nil, errInsufficientFundsForFee
   391  	}
   392  
   393  	if importedAVAXAmount > txFeeWithChange {
   394  		outs = append(outs, EVMOutput{
   395  			Address: to,
   396  			Amount:  importedAVAXAmount - txFeeWithChange,
   397  			AssetID: vm.ctx.AVAXAssetID,
   398  		})
   399  	}
   400  
   401  	// If no outputs are produced, return an error.
   402  	// Note: this can happen if there is exactly enough AVAX to pay the
   403  	// transaction fee, but no other funds to be imported.
   404  	if len(outs) == 0 {
   405  		return nil, errNoEVMOutputs
   406  	}
   407  
   408  	SortEVMOutputs(outs)
   409  
   410  	// Create the transaction
   411  	utx := &UnsignedImportTx{
   412  		NetworkID:      vm.ctx.NetworkID,
   413  		BlockchainID:   vm.ctx.ChainID,
   414  		Outs:           outs,
   415  		ImportedInputs: importedInputs,
   416  		SourceChain:    chainID,
   417  	}
   418  	tx := &Tx{UnsignedAtomicTx: utx}
   419  	if err := tx.Sign(vm.codec, signers); err != nil {
   420  		return nil, err
   421  	}
   422  	return tx, utx.Verify(vm.ctx, vm.currentRules())
   423  }
   424  
   425  // EVMStateTransfer performs the state transfer to increase the balances of
   426  // accounts accordingly with the imported EVMOutputs
   427  func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error {
   428  	for _, to := range utx.Outs {
   429  		if to.AssetID == ctx.AVAXAssetID {
   430  			log.Debug("crosschain", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", "AVAX")
   431  			// If the asset is AVAX, convert the input amount in nAVAX to gWei by
   432  			// multiplying by the x2c rate.
   433  			amount := new(big.Int).Mul(
   434  				new(big.Int).SetUint64(to.Amount), x2cRate)
   435  			state.AddBalance(to.Address, amount)
   436  		} else {
   437  			log.Debug("crosschain", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", to.AssetID)
   438  			amount := new(big.Int).SetUint64(to.Amount)
   439  			state.AddBalanceMultiCoin(to.Address, common.Hash(to.AssetID), amount)
   440  		}
   441  	}
   442  	return nil
   443  }