Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
eb8c0a0
sae: Add C-chain custom tx serialization
StephenButtolph Apr 25, 2026
e248997
sae: Implement AsOp
StephenButtolph Apr 25, 2026
253c255
wip
StephenButtolph Apr 25, 2026
3cca8aa
comments
StephenButtolph Apr 25, 2026
6346d2e
reduce exported identifiers
StephenButtolph Apr 25, 2026
ab83fe6
wip
StephenButtolph Apr 26, 2026
8f3f888
wip
StephenButtolph Apr 26, 2026
83cc38b
wip
StephenButtolph Apr 26, 2026
d606762
Extend fuzz tests
StephenButtolph Apr 26, 2026
0a82734
nit
StephenButtolph Apr 26, 2026
5b86b6b
wip
StephenButtolph Apr 26, 2026
3d92bd5
wip
StephenButtolph Apr 26, 2026
893e17e
wip
StephenButtolph Apr 26, 2026
83b0ec0
wip
StephenButtolph Apr 26, 2026
c46bcc2
nit
StephenButtolph Apr 26, 2026
e92638f
nit
StephenButtolph Apr 26, 2026
f2faffb
lint
StephenButtolph Apr 26, 2026
7447b08
lint
StephenButtolph Apr 26, 2026
8c3b6f0
nits
StephenButtolph Apr 27, 2026
0369966
fix bazel
StephenButtolph Apr 28, 2026
85ae854
ci: free runner disk before publishing Antithesis images (#5310)
JonathanOppenheimer Apr 28, 2026
88a234a
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 28, 2026
4e261c6
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 28, 2026
7073c57
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 28, 2026
d63857d
merged
StephenButtolph Apr 28, 2026
06d2a41
nit
StephenButtolph Apr 28, 2026
c28b2d2
fuzz full op
StephenButtolph Apr 28, 2026
aba7590
mark as compatibility
StephenButtolph Apr 28, 2026
ca2a5ea
nit
StephenButtolph Apr 28, 2026
4be05cf
nit
StephenButtolph Apr 28, 2026
9b4c83f
nit
StephenButtolph Apr 28, 2026
5d9dd0d
merged
StephenButtolph Apr 28, 2026
7e58c8e
nit
StephenButtolph Apr 28, 2026
8573565
nit
StephenButtolph Apr 28, 2026
2f01ee8
Unblock myself for sae progress
StephenButtolph Apr 29, 2026
b28d132
sae: Smarter tx fuzzing
StephenButtolph Apr 29, 2026
f12a1b4
nit
StephenButtolph Apr 29, 2026
258df6f
nit
StephenButtolph Apr 29, 2026
8cefe95
nit
StephenButtolph Apr 29, 2026
fa25c9a
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 29, 2026
95e7256
nit
StephenButtolph Apr 29, 2026
5d2ef23
want got
StephenButtolph Apr 29, 2026
46814f4
want got
StephenButtolph Apr 29, 2026
58306f5
Merge branch 'StephenButtolph/atomic-tx-rewrite.1' of github.com:ava-…
StephenButtolph Apr 29, 2026
88c09d1
merged
StephenButtolph Apr 29, 2026
271658d
nit
StephenButtolph Apr 29, 2026
9da6af8
nit
StephenButtolph Apr 29, 2026
7bcb962
ci
StephenButtolph Apr 29, 2026
34b1457
nosec
StephenButtolph Apr 29, 2026
692f899
merged
StephenButtolph Apr 29, 2026
eedd62e
nit
StephenButtolph Apr 29, 2026
e1ece39
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
9e3bb98
ci
StephenButtolph Apr 29, 2026
a4bad9b
remove duplicate code
StephenButtolph Apr 29, 2026
56850ce
nit
StephenButtolph Apr 29, 2026
a41e9a6
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
cefd09f
nit
StephenButtolph Apr 29, 2026
2ed8fcb
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
05ddba9
oops
StephenButtolph Apr 29, 2026
192a266
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
fca7808
no moar recursion pls
StephenButtolph Apr 29, 2026
f47cf40
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
1ede276
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 29, 2026
01cc4cf
Merge branch 'StephenButtolph/atomic-tx-rewrite.1' into StephenButtol…
StephenButtolph Apr 29, 2026
bc6aed5
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 29, 2026
0de4a41
nit
StephenButtolph Apr 29, 2026
87d29d2
nit
StephenButtolph Apr 29, 2026
6e9545c
Use the identifier rather than where the identifier is used
StephenButtolph Apr 30, 2026
47fe30d
Update visability
StephenButtolph Apr 30, 2026
adeaa57
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 30, 2026
6350008
merged
StephenButtolph Apr 30, 2026
05bb2f4
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
ba1afe1
nit
StephenButtolph Apr 30, 2026
3e3e6aa
ci
StephenButtolph Apr 30, 2026
9ffcd6c
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
963c295
remove jank, use cmp
StephenButtolph Apr 30, 2026
937c6f8
merged
StephenButtolph Apr 30, 2026
8ae1a82
fix
StephenButtolph Apr 30, 2026
3d11e3b
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
d39ed4d
nit
StephenButtolph Apr 30, 2026
18c3c2b
merged
StephenButtolph Apr 30, 2026
05930e4
missed one
StephenButtolph Apr 30, 2026
9e0ffe5
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
99f52e1
nit
StephenButtolph Apr 30, 2026
18a656e
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
8bb04f6
bazel
StephenButtolph Apr 30, 2026
e6a1d35
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
149b1cb
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.1
StephenButtolph Apr 30, 2026
a781d21
Merge branch 'StephenButtolph/atomic-tx-rewrite.1' into StephenButtol…
StephenButtolph Apr 30, 2026
23b9916
Add comment
StephenButtolph Apr 30, 2026
c1b9fa8
nit
StephenButtolph Apr 30, 2026
b4d98b5
nit
StephenButtolph Apr 30, 2026
81c3c02
nit
StephenButtolph Apr 30, 2026
9e36643
nit
StephenButtolph Apr 30, 2026
61dcfbd
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
ca7c26b
merged
StephenButtolph Apr 30, 2026
268f6ed
Merge branch 'StephenButtolph/smart-atomic-tx-fuzzing' into StephenBu…
StephenButtolph Apr 30, 2026
40c3ba6
cleanup
StephenButtolph Apr 30, 2026
3ed6547
cleanup
StephenButtolph Apr 30, 2026
f0515ec
Add more cases
StephenButtolph Apr 30, 2026
6dbe653
add comments
StephenButtolph Apr 30, 2026
9ae76cc
Add interface check
StephenButtolph Apr 30, 2026
6838983
nit
StephenButtolph Apr 30, 2026
d44b9e6
grammar
StephenButtolph Apr 30, 2026
5370371
more succinct
StephenButtolph Apr 30, 2026
cfd18da
merged
StephenButtolph Apr 30, 2026
a5c21d1
pedantic
StephenButtolph Apr 30, 2026
eb701ee
ok
StephenButtolph Apr 30, 2026
1ad0654
Document codec weirdness
StephenButtolph Apr 30, 2026
ac82445
simplify
StephenButtolph May 2, 2026
cab3300
Merge branch 'master' into StephenButtolph/atomic-tx-rewrite.2
StephenButtolph May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions vms/saevm/cchain/tx/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ go_library(
deps = [
"//codec",
"//codec/linearcodec",
"//graft/coreth/plugin/evm/atomic",
"//graft/coreth/plugin/evm/upgrade/ap5",
"//ids",
"//utils/hashing",
"//utils/math",
"//utils/wrappers",
"//vms/components/avax",
"//vms/components/gas",
"//vms/saevm/hook",
"//vms/secp256k1fx",
"@com_github_ava_labs_libevm//common",
"@com_github_holiman_uint256//:uint256",
],
)

Expand All @@ -39,14 +45,19 @@ go_test(
"//graft/coreth/plugin/evm/atomic",
"//graft/coreth/plugin/evm/atomic/vm",
"//ids",
"//snow",
"//utils/math",
"//vms/components/avax",
"//vms/components/gas",
"//vms/components/verify",
"//vms/saevm/cchain/tx/txtest",
"//vms/saevm/cmputils",
"//vms/saevm/hook",
"//vms/secp256k1fx",
"@com_github_ava_labs_libevm//common",
"@com_github_google_go_cmp//cmp",
"@com_github_google_go_cmp//cmp/cmpopts",
"@com_github_holiman_uint256//:uint256",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
Expand Down
101 changes: 101 additions & 0 deletions vms/saevm/cchain/tx/compatibility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@

import (
"encoding/json"
"math/big"
"testing"

"github.com/ava-labs/libevm/common"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/vms/components/gas"
"github.com/ava-labs/avalanchego/vms/saevm/cchain/tx/txtest"
"github.com/ava-labs/avalanchego/vms/saevm/hook"

. "github.com/ava-labs/avalanchego/vms/saevm/cchain/tx"
)
Expand Down Expand Up @@ -51,3 +59,96 @@
assert.JSONEq(t, string(want), string(got))
})
}

func FuzzAsOpCompatibility(f *testing.F) {
fuzz(f, func(t *testing.T, newTx *Tx) {
got, err := newTx.AsOp(AVAXAssetID)
Comment thread
JonathanOppenheimer marked this conversation as resolved.
if err != nil {
t.Skip("invalid tx")
}
Comment thread
StephenButtolph marked this conversation as resolved.

oldTx := ToOldTx(t, newTx)
gasUsed, err := oldTx.UnsignedAtomicTx.GasUsed(true)
require.NoErrorf(t, err, "%T.GasUsed(true)", oldTx.UnsignedAtomicTx)

gasPrice, err := atomic.EffectiveGasPrice(oldTx.UnsignedAtomicTx, AVAXAssetID, true)
require.NoErrorf(t, err, "atomic.EffectiveGasPrice(%T, avaxAssetID, true)", oldTx)

state := newAsOpStateDB()
if export, ok := oldTx.UnsignedAtomicTx.(*atomic.UnsignedExportTx); ok {
for _, in := range export.Ins {
state.initialNonces[in.Address] = in.Nonce
}
}

ctx := &snow.Context{AVAXAssetID: AVAXAssetID}
require.NoErrorf(t, oldTx.UnsignedAtomicTx.EVMStateTransfer(ctx, state), "%T.EVMStateTransfer()", oldTx.UnsignedAtomicTx)

want := hook.Op{
ID: oldTx.ID(),
Gas: gas.Gas(gasUsed),
GasFeeCap: gasPrice,
Burn: state.op.Burn,
Mint: state.op.Mint,
}
if diff := cmp.Diff(want, got, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("%T.AsOp() diff (-want +got):\n%s", newTx, diff)

Check warning on line 95 in vms/saevm/cchain/tx/compatibility_test.go

View workflow job for this annotation

GitHub Actions / Lint

`t.Errorf`" consider the assert and require libraries for succinct tests
}
})
}

// asOpStateDB is an in-memory [atomic.StateDB] for [FuzzAsOpCompatibility]. It
// constructs a [hook.Op] from [atomic.UnsignedAtomicTx.EVMStateTransfer].
type asOpStateDB struct {
initialNonces map[common.Address]uint64
op hook.Op
}

func newAsOpStateDB() *asOpStateDB {
return &asOpStateDB{
initialNonces: make(map[common.Address]uint64),
op: hook.Op{
Burn: make(map[common.Address]hook.AccountDebit),
Mint: make(map[common.Address]uint256.Int),
},
}
}

func (s *asOpStateDB) AddBalance(addr common.Address, amount *uint256.Int) {
b := s.op.Mint[addr]
b.Add(&b, amount)
s.op.Mint[addr] = b
}

func (s *asOpStateDB) SubBalance(addr common.Address, amount *uint256.Int) {
d := s.op.Burn[addr]
d.Amount.Add(&d.Amount, amount)
d.MinBalance = d.Amount
s.op.Burn[addr] = d
}

func (*asOpStateDB) GetBalance(common.Address) *uint256.Int {
// Large enough to never underflow, but small enough to never overflow.
return new(uint256.Int).Lsh(uint256.NewInt(1), 128)
}

func (*asOpStateDB) AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) {}

func (*asOpStateDB) SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) {}

func (*asOpStateDB) GetBalanceMultiCoin(common.Address, common.Hash) *big.Int {
// Large enough to never underflow, but small enough to never overflow.
return new(big.Int).Lsh(big.NewInt(1), 128)
}

func (s *asOpStateDB) SetNonce(addr common.Address, nonce uint64) {
d := s.op.Burn[addr]
// The op specifies what nonce is being consumed, not the next nonce. So we
// need to subtract 1.
d.Nonce = nonce - 1
s.op.Burn[addr] = d
}

func (s *asOpStateDB) GetNonce(addr common.Address) uint64 {
return s.initialNonces[addr]
}
74 changes: 71 additions & 3 deletions vms/saevm/cchain/tx/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@
package tx

import (
"errors"
"fmt"

"github.com/ava-labs/libevm/common"

// Imported for [atomic.UnsignedExportTx.Burned] comment resolution.
Comment thread
JonathanOppenheimer marked this conversation as resolved.
_ "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/saevm/hook"
)

var _ Unsigned = (*Export)(nil)

// Export is the unsigned component of a transaction that transfers assets from
Comment thread
StephenButtolph marked this conversation as resolved.
// the C-Chain to either the P-Chain or the X-Chain. It modifies the C-Chain
// state and produces UTXOs in the shared memory between the C-Chain and the
Expand All @@ -22,9 +32,6 @@ type Export struct {
ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"`
}

// TODO(StephenButtolph): Remove this with its removal from the interface.
func (*Export) isUnsigned() {}

// Input identifies an account + nonce pair on the C-Chain that authorizes the
// asset and quantity to deduct.
//
Expand All @@ -36,3 +43,64 @@ type Input struct {
AssetID ids.ID `serialize:"true" json:"assetID"`
Nonce uint64 `serialize:"true" json:"nonce"`
}

// Like [atomic.UnsignedExportTx.Burned], burned will error if the sum of the
// inputs exceeds MaxUint64, even if the total amount burned could be
// represented as a uint64.
//
// Because the total supply of AVAX fits in a uint64, this doesn't matter in
// practice and allows for easier fuzzing.
func (e *Export) burned(assetID ids.ID) (uint64, error) {
Comment thread
JonathanOppenheimer marked this conversation as resolved.
var (
burned uint64
err error
)
for _, in := range e.Ins {
if in.AssetID == assetID {
burned, err = math.Add(burned, in.Amount)
Comment thread
alarso16 marked this conversation as resolved.
if err != nil {
return 0, err
}
}
}
for _, out := range e.ExportedOutputs {
if out.Asset.ID == assetID {
burned, err = math.Sub(burned, out.Out.Amount())
if err != nil {
return 0, err
}
}
}
return burned, nil
}

func (e *Export) numSigs() (uint64, error) {
return uint64(len(e.Ins)), nil
}

var errMultipleNonces = errors.New("multiple nonces for address")

func (e *Export) asOp(avaxAssetID ids.ID) (op, error) {
burn := make(map[common.Address]hook.AccountDebit, len(e.Ins))
for _, in := range e.Ins {
debit, ok := burn[in.Address]
if ok && debit.Nonce != in.Nonce {
return op{}, fmt.Errorf("%w: address %s has nonces %d and %d", errMultipleNonces, in.Address, debit.Nonce, in.Nonce)
}

// Even if no AVAX is debited, non-AVAX inputs MUST increment the nonce.
if in.AssetID == avaxAssetID {
amount := scaleAVAX(in.Amount)
if _, overflow := debit.Amount.AddOverflow(&debit.Amount, &amount); overflow {
return op{}, fmt.Errorf("%w: for address %s", errOverflow, in.Address)
}
}

debit.Nonce = in.Nonce
debit.MinBalance = debit.Amount
burn[in.Address] = debit
}
return op{
burn: burn,
}, nil
}
81 changes: 78 additions & 3 deletions vms/saevm/cchain/tx/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
package tx

import (
"errors"
"fmt"

"github.com/ava-labs/libevm/common"
"github.com/holiman/uint256"

// Imported for [atomic.UnsignedImportTx.Burned] comment resolution.
_ "github.com/ava-labs/avalanchego/graft/coreth/plugin/evm/atomic"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
)

var _ Unsigned = (*Import)(nil)

// Import is the unsigned component of a transaction that transfers assets from
// either the P-Chain or the X-Chain to the C-Chain. It consumes UTXOs in the
// shared memory between the C-Chain and the source chain and increases balances
Expand All @@ -22,9 +33,6 @@ type Import struct {
Outs []Output `serialize:"true" json:"outputs"`
}

// TODO(StephenButtolph): Remove this with its removal from the interface.
func (*Import) isUnsigned() {}

// Output specifies an account on the C-Chain whose balance of the specified
// asset should be increased.
//
Expand All @@ -35,3 +43,70 @@ type Output struct {
Amount uint64 `serialize:"true" json:"amount"`
AssetID ids.ID `serialize:"true" json:"assetID"`
}

// Like [atomic.UnsignedImportTx.Burned], burned will error if the sum of the
// inputs exceeds MaxUint64, even if the total amount burned could be
// represented as a uint64.
//
// Because the total supply of AVAX fits in a uint64, this doesn't matter in
// practice and allows for easier fuzzing.
func (i *Import) burned(assetID ids.ID) (uint64, error) {
var (
burned uint64
err error
)
for _, in := range i.ImportedInputs {
if in.Asset.ID == assetID {
burned, err = math.Add(burned, in.In.Amount())
if err != nil {
return 0, err
}
}
}
for _, out := range i.Outs {
if out.AssetID == assetID {
burned, err = math.Sub(burned, out.Amount)
if err != nil {
return 0, err
}
}
}
return burned, nil
}

var errUnexpectedInputType = errors.New("unexpected input type")

func (i *Import) numSigs() (uint64, error) {
var n uint64
for _, in := range i.ImportedInputs {
input, ok := in.In.(*secp256k1fx.TransferInput)
if !ok {
return 0, fmt.Errorf("%w: got %T ; want %T", errUnexpectedInputType, in.In, input)
}
n += uint64(len(input.SigIndices))
}
return n, nil
}

var errOverflow = errors.New("amount overflow")

func (i *Import) asOp(avaxAssetID ids.ID) (op, error) {
mint := make(map[common.Address]uint256.Int, len(i.Outs))
for _, out := range i.Outs {
if out.AssetID != avaxAssetID {
continue
}

var (
total = mint[out.Address]
amount = scaleAVAX(out.Amount)
)
if _, overflow := total.AddOverflow(&total, &amount); overflow {
return op{}, fmt.Errorf("%w: for address %s", errOverflow, out.Address)
}
mint[out.Address] = total
}
return op{
mint: mint,
}, nil
}
Loading
Loading