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 }