diff --git a/vms/saevm/cchain/tx/BUILD.bazel b/vms/saevm/cchain/tx/BUILD.bazel index c21e751f99a2..e73e171da14d 100644 --- a/vms/saevm/cchain/tx/BUILD.bazel +++ b/vms/saevm/cchain/tx/BUILD.bazel @@ -16,6 +16,7 @@ go_library( ], importpath = "github.com/ava-labs/avalanchego/vms/saevm/cchain/tx", deps = [ + "//chains/atomic", "//codec", "//codec/linearcodec", "//graft/coreth/plugin/evm/atomic", @@ -42,6 +43,7 @@ go_test( data = glob(["testdata/**"]), embed = [":tx"], deps = [ + "//chains/atomic", "//graft/coreth/plugin/evm/atomic", "//graft/coreth/plugin/evm/atomic/vm", "//ids", diff --git a/vms/saevm/cchain/tx/compatibility_test.go b/vms/saevm/cchain/tx/compatibility_test.go index 874450767efd..a62d254e74c3 100644 --- a/vms/saevm/cchain/tx/compatibility_test.go +++ b/vms/saevm/cchain/tx/compatibility_test.go @@ -152,3 +152,16 @@ func (s *asOpStateDB) SetNonce(addr common.Address, nonce uint64) { func (s *asOpStateDB) GetNonce(addr common.Address) uint64 { return s.initialNonces[addr] } + +func FuzzAtomicRequestsCompatibility(f *testing.F) { + fuzz(f, func(t *testing.T, newTx *Tx) { + oldTx := ToOldTx(t, newTx) + wantChainID, wantRequests, err := oldTx.UnsignedAtomicTx.AtomicOps() + require.NoErrorf(t, err, "%T.AtomicOps()", oldTx.UnsignedAtomicTx) + + gotChainID, gotRequests, err := newTx.AtomicRequests() + require.NoErrorf(t, err, "%T.AtomicRequests()", newTx) + assert.Equal(t, wantChainID, gotChainID, "chainID") + assert.Equal(t, wantRequests, gotRequests, "requests") + }) +} diff --git a/vms/saevm/cchain/tx/export.go b/vms/saevm/cchain/tx/export.go index 0681045b003e..236ed6e4d957 100644 --- a/vms/saevm/cchain/tx/export.go +++ b/vms/saevm/cchain/tx/export.go @@ -16,6 +16,8 @@ import ( "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/saevm/hook" + + chainsatomic "github.com/ava-labs/avalanchego/chains/atomic" ) var _ Unsigned = (*Export)(nil) @@ -104,3 +106,33 @@ func (e *Export) asOp(avaxAssetID ids.ID) (op, error) { burn: burn, }, nil } + +func (e *Export) atomicRequests(txID ids.ID) (ids.ID, *chainsatomic.Requests, error) { + elems := make([]*chainsatomic.Element, len(e.ExportedOutputs)) + for i, out := range e.ExportedOutputs { + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(i), //#nosec G115 -- Won't overflow + }, + Asset: out.Asset, + Out: out.Out, + } + + utxoBytes, err := c.Marshal(codecVersion, utxo) + if err != nil { + return ids.ID{}, nil, err + } + utxoID := utxo.InputID() + elem := &chainsatomic.Element{ + Key: utxoID[:], + Value: utxoBytes, + } + if o, ok := utxo.Out.(avax.Addressable); ok { + elem.Traits = o.Addresses() + } + + elems[i] = elem + } + return e.DestinationChain, &chainsatomic.Requests{PutRequests: elems}, nil +} diff --git a/vms/saevm/cchain/tx/import.go b/vms/saevm/cchain/tx/import.go index 262562398bf3..ce35ffd8281a 100644 --- a/vms/saevm/cchain/tx/import.go +++ b/vms/saevm/cchain/tx/import.go @@ -17,6 +17,8 @@ import ( "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + chainsatomic "github.com/ava-labs/avalanchego/chains/atomic" ) var _ Unsigned = (*Import)(nil) @@ -110,3 +112,12 @@ func (i *Import) asOp(avaxAssetID ids.ID) (op, error) { mint: mint, }, nil } + +func (i *Import) atomicRequests(ids.ID) (ids.ID, *chainsatomic.Requests, error) { + utxoIDs := make([][]byte, len(i.ImportedInputs)) + for j, in := range i.ImportedInputs { + inputID := in.InputID() + utxoIDs[j] = inputID[:] + } + return i.SourceChain, &chainsatomic.Requests{RemoveRequests: utxoIDs}, nil +} diff --git a/vms/saevm/cchain/tx/tx.go b/vms/saevm/cchain/tx/tx.go index 9509736401d4..95b47a1bc427 100644 --- a/vms/saevm/cchain/tx/tx.go +++ b/vms/saevm/cchain/tx/tx.go @@ -22,6 +22,8 @@ import ( "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/saevm/hook" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + chainsatomic "github.com/ava-labs/avalanchego/chains/atomic" ) // Tx is a signed transaction that interacts with shared memory. @@ -48,6 +50,10 @@ type Unsigned interface { // asOp returns the operation that this transaction performs on the EVM // state. asOp(avaxAssetID ids.ID) (op, error) + + // atomicRequests returns the operations that should be applied to shared + // memory when this transaction is executed. + atomicRequests(txID ids.ID) (chainID ids.ID, r *chainsatomic.Requests, err error) } // op contains the state changes of [hook.Op] @@ -179,6 +185,12 @@ func gasPrice(cost uint64, gas gas.Gas) uint256.Int { return p } +// AtomicRequests returns shared-memory modifications that this transaction +// should perform on the peer chainID during execution. +func (t *Tx) AtomicRequests() (chainID ids.ID, r *chainsatomic.Requests, err error) { + return t.atomicRequests(t.ID()) +} + // Parse deserializes a [Tx] from its canonical binary format. func Parse(b []byte) (*Tx, error) { var tx Tx diff --git a/vms/saevm/cchain/tx/tx_test.go b/vms/saevm/cchain/tx/tx_test.go index 654a17dca39a..9999ce3fc8d7 100644 --- a/vms/saevm/cchain/tx/tx_test.go +++ b/vms/saevm/cchain/tx/tx_test.go @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/avalanchego/vms/saevm/hook" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + chainsatomic "github.com/ava-labs/avalanchego/chains/atomic" safemath "github.com/ava-labs/avalanchego/utils/math" ) @@ -35,12 +36,14 @@ import ( var ( AVAXAssetID = ids.FromStringOrPanic("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z") Tests = [...]struct { - Name string - Old *atomic.Tx - New *Tx - JSON string - Bytes []byte - Op hook.Op + Name string + Old *atomic.Tx + New *Tx + JSON string + Bytes []byte + Op hook.Op + AtomicRequestsChainID ids.ID + AtomicRequests *chainsatomic.Requests }{ { Name: "import", // Included in https://subnets.avax.network/c-chain/block/4 @@ -147,6 +150,12 @@ var ( common.HexToAddress("0xb8b5a87d1c05676f1f966da49151fa54dbe68c33"): scaleAVAX(50_000_000), }, }, + AtomicRequestsChainID: ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM"), + AtomicRequests: &chainsatomic.Requests{ + RemoveRequests: [][]byte{ + common.FromHex("0xfd9e10917c4a2dab395683cfb766cdc584eba118bc22d3d0fc356fb79345cf64"), + }, + }, }, { Name: "export", // Included in https://subnets.avax.network/c-chain/block/48 @@ -256,6 +265,16 @@ var ( }, }, }, + AtomicRequestsChainID: ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM"), + AtomicRequests: &chainsatomic.Requests{ + PutRequests: []*chainsatomic.Element{{ + Key: common.FromHex("0x38ebe8fc127b2eaeeb25c72a747e0ef27460fb04b5929568ed959d67ec3e4948"), + Value: common.FromHex("0x000067b5812292324365c6e2a479b2601cd1cd1facc2fcc8c29d58b5ed96583ea17e0000000021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000007000000000000000100000000000000000000000100000001d6ce17826dd7c12a7577af257e82d99143b72500"), + Traits: [][]byte{ + ids.ShortFromStringOrPanic("LanVZgBDVvtarbTXD1uU7r1nXVJyLmPUz").Bytes(), + }, + }}, + }, }, { Name: "import_multi_input", // Included in https://subnets.avax.network/c-chain/block/132481 @@ -465,6 +484,14 @@ var ( common.HexToAddress("0x383c293db6be7ac246f0956ad632344dc2cd1da3"): scaleAVAX(597_000_000), }, }, + AtomicRequestsChainID: ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM"), + AtomicRequests: &chainsatomic.Requests{ + RemoveRequests: [][]byte{ + common.FromHex("0x821514ed5d925142159bc2c78bc56b043200e53aab79e97ca75e7ca7f6a96d05"), + common.FromHex("0xea05e5c7135613b689d9f6b9903f431067ed72a2957ca82a652de1e8fef2c630"), + common.FromHex("0xd71fb48751f6d5732e7ff63168ed311b40bf517b36279e326878fc3f5169a656"), + }, + }, }, { Name: "export_same_address_multi_asset", // Synthetic @@ -481,7 +508,27 @@ var ( Nonce: 5, }, }, - ExportedOutputs: []*avax.TransferableOutput{}, + ExportedOutputs: []*avax.TransferableOutput{ + { + Out: &secp256k1fx.TransferOutput{ + Amt: 100, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{{0xaa}}, + }, + }, + }, + { + Asset: avax.Asset{ID: AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 100_000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{{0xaa}}, + }, + }, + }, + }, }, Creds: []verify.Verifiable{}, }, @@ -498,7 +545,27 @@ var ( Nonce: 5, }, }, - ExportedOutputs: []*avax.TransferableOutput{}, + ExportedOutputs: []*avax.TransferableOutput{ + { + Out: &secp256k1fx.TransferOutput{ + Amt: 100, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{{0xaa}}, + }, + }, + }, + { + Asset: avax.Asset{ID: AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 100_000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{{0xaa}}, + }, + }, + }, + }, }, Creds: []Credential{}, }, @@ -511,15 +578,36 @@ var ( {"address":"0x0000000000000000000000000000000000000000","amount":999,"assetID":"11111111111111111111111111111111LpoYY","nonce":5}, {"address":"0x0000000000000000000000000000000000000000","amount":1000000,"assetID":"FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z","nonce":5} ], - "exportedOutputs":[] + "exportedOutputs":[ + { + "assetID":"11111111111111111111111111111111LpoYY", + "fxID":"11111111111111111111111111111111LpoYY", + "output":{ + "addresses":["GVsscSys19nXbNEJi5g1Z1y8UawXee8gj"], + "amount":100, + "locktime":0, + "threshold":1 + } + }, + { + "assetID":"FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID":"11111111111111111111111111111111LpoYY", + "output":{ + "addresses":["GVsscSys19nXbNEJi5g1Z1y8UawXee8gj"], + "amount":100000, + "locktime":0, + "threshold":1 + } + } + ] }, "credentials":[] }`, - Bytes: common.FromHex("0x000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000f424021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000000000000050000000000000000"), + Bytes: common.FromHex("0x000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000f424021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff000000000000000500000002000000000000000000000000000000000000000000000000000000000000000000000007000000000000006400000000000000000000000100000001aa0000000000000000000000000000000000000021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff0000000700000000000186a000000000000000000000000100000001aa0000000000000000000000000000000000000000000000"), Op: hook.Op{ - ID: ids.FromStringOrPanic("29cCETWxEUN1QCuex59j46Xtr8urBRo5M7HzwBqC3qDXWd73sX"), - Gas: 12218, - GasFeeCap: *uint256.NewInt(1_000_000 * _x2cRate / 12218), + ID: ids.FromStringOrPanic("dLYGLJkvGarYPHnfRqK8zH9nu6dj6Ajf1Wjtm8X7fxr5jvvL7"), + Gas: 12378, + GasFeeCap: *uint256.NewInt(900_000 * _x2cRate / 12378), Burn: map[common.Address]hook.AccountDebit{ {}: { Nonce: 5, @@ -528,6 +616,24 @@ var ( }, }, }, + AtomicRequests: &chainsatomic.Requests{ + PutRequests: []*chainsatomic.Element{ + { + Key: common.FromHex("0x82c024362a71c075ac15e5e000dd66380907e3ea6af121d3d78478bb07848b75"), + Value: common.FromHex("0x00005281e076436407df7c97e5abaff7f63e11bd8bc9ce03c787f12ee0e21fe68dca00000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000006400000000000000000000000100000001aa00000000000000000000000000000000000000"), + Traits: [][]byte{ + ids.ShortID{0xaa}.Bytes(), + }, + }, + { + Key: common.FromHex("0x836913bdcf743940c51675ac186dc415cbb5f2a7309916f4a48bda3df5334245"), + Value: common.FromHex("0x00005281e076436407df7c97e5abaff7f63e11bd8bc9ce03c787f12ee0e21fe68dca0000000121e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff0000000700000000000186a000000000000000000000000100000001aa00000000000000000000000000000000000000"), + Traits: [][]byte{ + ids.ShortID{0xaa}.Bytes(), + }, + }, + }, + }, }, { Name: "export_multi_address_multi_asset", // Synthetic @@ -546,7 +652,27 @@ var ( Nonce: 7, }, }, - ExportedOutputs: []*avax.TransferableOutput{}, + ExportedOutputs: []*avax.TransferableOutput{ + { + Out: &secp256k1fx.TransferOutput{ + Amt: 500, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 2, + Addrs: []ids.ShortID{{0xbb}, {0xcc}}, + }, + }, + }, + { + Asset: avax.Asset{ID: AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 500_000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 2, + Addrs: []ids.ShortID{{0xbb}, {0xcc}}, + }, + }, + }, + }, }, Creds: []verify.Verifiable{}, }, @@ -565,7 +691,27 @@ var ( Nonce: 7, }, }, - ExportedOutputs: []*avax.TransferableOutput{}, + ExportedOutputs: []*avax.TransferableOutput{ + { + Out: &secp256k1fx.TransferOutput{ + Amt: 500, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 2, + Addrs: []ids.ShortID{{0xbb}, {0xcc}}, + }, + }, + }, + { + Asset: avax.Asset{ID: AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 500_000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 2, + Addrs: []ids.ShortID{{0xbb}, {0xcc}}, + }, + }, + }, + }, }, Creds: []Credential{}, }, @@ -578,15 +724,36 @@ var ( {"address":"0x0100000000000000000000000000000000000000","amount":999,"assetID":"11111111111111111111111111111111LpoYY","nonce":5}, {"address":"0x0200000000000000000000000000000000000000","amount":1000000,"assetID":"FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z","nonce":7} ], - "exportedOutputs":[] + "exportedOutputs":[ + { + "assetID":"11111111111111111111111111111111LpoYY", + "fxID":"11111111111111111111111111111111LpoYY", + "output":{ + "addresses":["J3mMsbNx1AfUrQMSHBwWcDfYRYY1i7rGE","Kber8jn31BYS7SUZrJD1fRMxNW8MvZnhY"], + "amount":500, + "locktime":0, + "threshold":2 + } + }, + { + "assetID":"FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID":"11111111111111111111111111111111LpoYY", + "output":{ + "addresses":["J3mMsbNx1AfUrQMSHBwWcDfYRYY1i7rGE","Kber8jn31BYS7SUZrJD1fRMxNW8MvZnhY"], + "amount":500000, + "locktime":0, + "threshold":2 + } + } + ] }, "credentials":[] }`, - Bytes: common.FromHex("0x000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002010000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000000000000000000000005020000000000000000000000000000000000000000000000000f424021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000000000000070000000000000000"), + Bytes: common.FromHex("0x000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002010000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000000000000000000000005020000000000000000000000000000000000000000000000000f424021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000000000000070000000200000000000000000000000000000000000000000000000000000000000000000000000700000000000001f400000000000000000000000200000002bb00000000000000000000000000000000000000cc0000000000000000000000000000000000000021e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000007000000000007a12000000000000000000000000200000002bb00000000000000000000000000000000000000cc0000000000000000000000000000000000000000000000"), Op: hook.Op{ - ID: ids.FromStringOrPanic("8P9XRKhxHeTv3t4Aj9cTV6dD5h78WVFH8nctLuCkeSavfKeEG"), - Gas: 12218, - GasFeeCap: *uint256.NewInt(1_000_000 * _x2cRate / 12218), + ID: ids.FromStringOrPanic("2cfgJ1XjwjNVvF4ZoW86Sc77z7TDMyGB33edRioEfuLkyKkkob"), + Gas: 12418, + GasFeeCap: *uint256.NewInt(500_000 * _x2cRate / 12418), Burn: map[common.Address]hook.AccountDebit{ {1}: { Nonce: 5, @@ -598,6 +765,26 @@ var ( }, }, }, + AtomicRequests: &chainsatomic.Requests{ + PutRequests: []*chainsatomic.Element{ + { + Key: common.FromHex("0x150b950ce35ef7c512b1dec725164c8ee170728976ca0c9c1202eb60cafe9230"), + Value: common.FromHex("0x0000d4ae9ab4d296ced15beba7686a2216bcfdf4a7f1bcc502b3e5c0c79a4601b2ef0000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000001f400000000000000000000000200000002bb00000000000000000000000000000000000000cc00000000000000000000000000000000000000"), + Traits: [][]byte{ + ids.ShortID{0xbb}.Bytes(), + ids.ShortID{0xcc}.Bytes(), + }, + }, + { + Key: common.FromHex("0x3080252925d9e3e399292cfecc8f95bb03da84645f44b8d0da82dc472e1f41d2"), + Value: common.FromHex("0x0000d4ae9ab4d296ced15beba7686a2216bcfdf4a7f1bcc502b3e5c0c79a4601b2ef0000000121e67317cbc4be2aeb00677ad6462778a8f52274b9d605df2591b23027a87dff00000007000000000007a12000000000000000000000000200000002bb00000000000000000000000000000000000000cc00000000000000000000000000000000000000"), + Traits: [][]byte{ + ids.ShortID{0xbb}.Bytes(), + ids.ShortID{0xcc}.Bytes(), + }, + }, + }, + }, }, { Name: "import_non_avax", // Synthetic @@ -659,6 +846,11 @@ var ( Gas: 10226, Mint: map[common.Address]uint256.Int{}, }, + AtomicRequests: &chainsatomic.Requests{ + RemoveRequests: [][]byte{ + common.FromHex("0x2c34ce1df23b838c5abf2a7f6437cca3d3067ed509ff25f11df6b11b582b51eb"), + }, + }, }, } OldTxs []*atomic.Tx @@ -1107,3 +1299,14 @@ func TestAsOp_Errors(t *testing.T) { }) } } + +func TestAtomicRequests(t *testing.T) { + for _, test := range Tests { + t.Run(test.Name, func(t *testing.T) { + chainID, requests, err := test.New.AtomicRequests() + require.NoErrorf(t, err, "%T.AtomicRequests()", test.New) + assert.Equalf(t, test.AtomicRequestsChainID, chainID, "%T.AtomicRequests().ChainID", test.New) + assert.Equalf(t, test.AtomicRequests, requests, "%T.AtomicRequests().Requests", test.New) + }) + } +}