Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions vms/saevm/sae/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ go_test(
"@com_github_ava_labs_libevm//crypto",
"@com_github_ava_labs_libevm//eth/tracers/logger",
"@com_github_ava_labs_libevm//ethclient",
"@com_github_ava_labs_libevm//ethclient/gethclient",
"@com_github_ava_labs_libevm//ethdb",
"@com_github_ava_labs_libevm//libevm",
"@com_github_ava_labs_libevm//libevm/ethapi",
Expand Down Expand Up @@ -209,6 +210,7 @@ go_test(
"@com_github_ava_labs_libevm//crypto",
"@com_github_ava_labs_libevm//eth/tracers/logger",
"@com_github_ava_labs_libevm//ethclient",
"@com_github_ava_labs_libevm//ethclient/gethclient",
"@com_github_ava_labs_libevm//ethdb",
"@com_github_ava_labs_libevm//libevm",
"@com_github_ava_labs_libevm//libevm/ethapi",
Expand Down
13 changes: 11 additions & 2 deletions vms/saevm/sae/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,27 @@ func (b *backend) server(filter *filters.FilterAPI) (*rpc.Server, error) {
{"eth", ethapi.NewEthereumAPI(b)},
// Standard Ethereum node APIs:
// - eth_blockNumber
// - eth_call
// - eth_chainId
// - eth_estimateGas
// - eth_getBalance
// - eth_getBlockByHash
// - eth_getBlockByNumber
// - eth_getBlockReceipts
// - eth_getCode
// - eth_getProof
// - eth_getStorageAt
// - eth_getUncleByBlockHashAndIndex
// - eth_getUncleByBlockNumberAndIndex
// - eth_getUncleCountByBlockHash
// - eth_getUncleCountByBlockNumber
//
// geth-specific APIs:
// Geth-specific APIs:
// - eth_createAccessList
// - eth_getHeaderByHash
// - eth_getHeaderByNumber
//
// Undocumented APIs:
// - eth_getBlockReceipts
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This RPC is actually already supported/tested, but it wasn't included in the list. When going through the list to add the ones for this PR, I thought I'd include it.

{"eth", &blockChainAPI{ethapi.NewBlockChainAPI(b), b}},
// Standard Ethereum node APIs:
// - eth_getBlockTransactionCountByHash
Expand Down
27 changes: 2 additions & 25 deletions vms/saevm/sae/rpc_custom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,30 +136,7 @@ func TestCallDetailed(t *testing.T) {
}
}))

deploy := &types.LegacyTx{
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CreationCode(),
}

escrowAddr := crypto.CreateAddress(sut.wallet.Addresses()[0], 0)
recv := common.Address{'r', 'e', 'c', 'v'}
const depositVal = 42
deposit := &types.LegacyTx{
To: &escrowAddr,
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CallDataToDeposit(recv),
Value: big.NewInt(depositVal),
}

sign := sut.wallet.SetNonceAndSign
b := sut.runConsensusLoop(t, sign(t, 0, deploy), sign(t, 0, deposit))
require.Len(t, b.Transactions(), 2, "tx count")
require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b)
for _, r := range b.Receipts() {
require.Equalf(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r)
}
_, escrowAddr, recv, _ := sut.deployEscrow(ctx, t, big.NewInt(escrowDepositVal))

const revertWith = 12345
revertAsPanic := slices.Concat(
Expand All @@ -186,7 +163,7 @@ func TestCallDetailed(t *testing.T) {
},
want: saerpc.DetailedExecutionResult{
UsedGas: 23675,
ReturnData: uint256.NewInt(depositVal).PaddedBytes(32),
ReturnData: uint256.NewInt(escrowDepositVal).PaddedBytes(32),
},
},
{
Expand Down
158 changes: 94 additions & 64 deletions vms/saevm/sae/rpc_stateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"github.com/ava-labs/libevm/core/vm"
"github.com/ava-labs/libevm/crypto"
"github.com/ava-labs/libevm/eth/tracers/logger"
"github.com/ava-labs/libevm/ethclient/gethclient"
"github.com/ava-labs/libevm/params"
"github.com/ava-labs/libevm/rpc"
"github.com/google/go-cmp/cmp"
Expand All @@ -28,7 +29,6 @@
"github.com/ava-labs/avalanchego/vms/saevm/saetest/escrow"

saeparams "github.com/ava-labs/avalanchego/vms/saevm/params"
ethereum "github.com/ava-labs/libevm"
)

// TestStateQueryOnNonCanonicalBlock verifies that state-dependent RPC calls
Expand Down Expand Up @@ -92,30 +92,15 @@
func TestDebugTrace(t *testing.T) {
ctx, sut := newSUT(t, 1)

escrowAddr := crypto.CreateAddress(sut.wallet.Addresses()[0], 0)
recv := common.Address{'r', 'e', 'c', 'v'}
const depositVal = 42

sign := sut.wallet.SetNonceAndSign
deployTx := sign(t, 0, &types.LegacyTx{
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CreationCode(),
})
depositTx := sign(t, 0, &types.LegacyTx{
To: &escrowAddr,
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CallDataToDeposit(recv),
Value: big.NewInt(depositVal),
})

b := sut.runConsensusLoop(t, deployTx, depositTx)
require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b)
require.Lenf(t, b.Receipts(), 2, "%T.Receipts()", b)
for _, r := range b.Receipts() {
require.Equalf(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r)
}
b, _, recv, _ := sut.deployEscrow(ctx, t, big.NewInt(escrowDepositVal))
// deployEscrow includes the deploy tx at index 0 and the deposit tx at
// index 1.
const (
deployTxIdx = 0
depositTxIdx = 1
)
deployTxHash := b.Transactions()[deployTxIdx].Hash()
depositTxHash := b.Transactions()[depositTxIdx].Hash()

// Specifying the entire trace would be excessive and uninformative so we
// select a precise location of an event associated with the deposit()
Expand All @@ -137,23 +122,23 @@
Error string `json:"error"`
}{
{
TxHash: deployTx.Hash(),
TxHash: deployTxHash,
Result: &logger.ExecutionResult{
Gas: b.Receipts()[0].GasUsed,
Gas: b.Receipts()[deployTxIdx].GasUsed,
ReturnValue: common.Bytes2Hex(escrow.ByteCode()),
StructLogs: []logger.StructLogRes{},
},
},
{
TxHash: depositTx.Hash(),
TxHash: depositTxHash,
Result: &logger.ExecutionResult{
Gas: b.Receipts()[1].GasUsed,
Gas: b.Receipts()[depositTxIdx].GasUsed,
StructLogs: []logger.StructLogRes{{
Pc: logPC,
Op: vm.LOG1.String(),
Depth: 1,
Stack: utils.PointerTo([]string{
escrow.DepositEvent(recv, uint256.NewInt(depositVal)).Topics[0].String(),
escrow.DepositEvent(recv, uint256.NewInt(escrowDepositVal)).Topics[0].String(),
"0x40", "0x80", // arbitrary memory locations selected by Solidity
}),
}},
Expand Down Expand Up @@ -199,34 +184,11 @@
sut.testRPC(ctx, t, tests...)
}

func TestEthCall(t *testing.T) {
func TestStatefulRPCs(t *testing.T) {
opt, vmTime := withVMTime(t, time.Unix(saeparams.TauSeconds, 0))
ctx, sut := newSUT(t, 1, opt)

deploy := &types.LegacyTx{
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CreationCode(),
}

escrowAddr := crypto.CreateAddress(sut.wallet.Addresses()[0], 0)
recv := common.Address{'r', 'e', 'c', 'v'}
const val = 42
deposit := &types.LegacyTx{
To: &escrowAddr,
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CallDataToDeposit(recv),
Value: big.NewInt(val),
}

sign := sut.wallet.SetNonceAndSign
b := sut.runConsensusLoop(t, sign(t, 0, deploy), sign(t, 0, deposit))
require.Len(t, b.Transactions(), 2, "tx count")
require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b)
for _, r := range b.Receipts() {
require.Equalf(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r)
}
b, escrowAddr, recv, callMsg := sut.deployEscrow(ctx, t, big.NewInt(escrowDepositVal))

vmTime.advanceToSettle(ctx, t, b)
for range 2 {
Expand All @@ -236,6 +198,21 @@
_, ok := sut.rawVM.consensusCritical.Load(b.Hash())
require.Falsef(t, ok, "%T[%#x] still in VM memory", b, b.Hash())

// Storage key for balances[recv] at mapping slot 0:
// keccak256(abi.encode(address, uint256(0)))
storageKey := crypto.Keccak256Hash(
common.LeftPadBytes(recv.Bytes(), 32),
common.Hash{}.Bytes(),
Comment on lines +204 to +205
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/question: should we actually use abi.encode from abi package?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how much of an improvement that would be -- I think manually encoding is fine. There's a lot of ABI setup required, and the goal is test the RPCs. If you feel strongly I can change it.

)
storageKeyHex := storageKey.Hex()

gc := gethclient.New(sut.rpcClient)

wantBalance := big.NewInt(escrowDepositVal)
wantStorageValue := big.NewInt(escrowDepositVal)
wantStorageBytes := uint256.NewInt(escrowDepositVal).PaddedBytes(32)
wantCode := escrow.ByteCode()

tests := []struct {
name string
num rpc.BlockNumber
Expand All @@ -251,15 +228,68 @@
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
msg := ethereum.CallMsg{
To: &escrowAddr,
Data: escrow.CallDataForBalance(recv),
}

got, err := sut.CallContract(ctx, msg, big.NewInt(int64(tt.num)))
t.Logf("%T.CallContract(%+v, %d)", sut.Client, msg, tt.num) // avoids having to repeat in failure messages
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name hierarchy now provides this information

require.NoError(t, err)
assert.Equal(t, uint256.NewInt(val).PaddedBytes(32), got)
blockNum := big.NewInt(int64(tt.num))

t.Run("eth_call", func(t *testing.T) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got, err := sut.CallContract(ctx, callMsg, blockNum)
require.NoError(t, err, "CallContract()")
assert.Equal(t, wantStorageBytes, got, "CallContract() result")

Check warning on line 236 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Equal`" continues on failure; consider require for fail-fast
})

t.Run("eth_getBalance", func(t *testing.T) {
got, err := sut.BalanceAt(ctx, escrowAddr, blockNum)
require.NoError(t, err, "BalanceAt()")
require.Zero(t, wantBalance.Cmp(got), "BalanceAt(): want %d, got %s", escrowDepositVal, got)
})

t.Run("eth_getCode", func(t *testing.T) {
got, err := sut.CodeAt(ctx, escrowAddr, blockNum)
require.NoError(t, err, "CodeAt()")
assert.Equal(t, wantCode, got, "CodeAt() result")

Check warning on line 248 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Equal`" continues on failure; consider require for fail-fast
})

t.Run("eth_getStorageAt", func(t *testing.T) {
got, err := sut.StorageAt(ctx, escrowAddr, storageKey, blockNum)
require.NoError(t, err, "StorageAt()")
assert.Equal(t, wantStorageBytes, got, "StorageAt() result")

Check warning on line 254 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Equal`" continues on failure; consider require for fail-fast
})

t.Run("eth_getProof", func(t *testing.T) {
got, err := gc.GetProof(ctx, escrowAddr, []string{storageKeyHex}, blockNum)
require.NoError(t, err, "GetProof()")
require.NotNil(t, got, "GetProof() result")

assert.NotEmpty(t, got.AccountProof, "GetProof() accountProof")

Check warning on line 262 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.NotEmpty`" continues on failure; consider require for fail-fast
require.Zero(t, wantBalance.Cmp(got.Balance), "GetProof() balance: want %d, got %s", escrowDepositVal, got.Balance)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wantBalance instead, applicable to other lines


require.Len(t, got.StorageProof, 1, "GetProof() storageProof length")
assert.NotEmpty(t, got.StorageProof[0].Proof, "GetProof() storageProof[0].Proof")

Check warning on line 266 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.NotEmpty`" continues on failure; consider require for fail-fast
require.Zero(t, wantStorageValue.Cmp(got.StorageProof[0].Value), "GetProof() storageProof[0].Value: want %d, got %s", escrowDepositVal, got.StorageProof[0].Value)
})
})
}
}

// TestStatefulRPCsLatestOnly tests stateful RPC methods that don't accept a
// block number parameter via ethclient/gethclient and so always run against
// the latest block: eth_estimateGas and eth_createAccessList.
func TestStatefulRPCsLatestOnly(t *testing.T) {
ctx, sut := newSUT(t, 1)

_, _, _, callMsg := sut.deployEscrow(ctx, t, nil)

t.Run("eth_estimateGas", func(t *testing.T) {
got, err := sut.EstimateGas(ctx, callMsg)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests are inherently different, given that they don't need to run in the table. I would put them in a separate test, and just create a new callMsg

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can do so, but there is just a lot of shared setup for these, which is why I put them in here... I can see how I can split them out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can actually do a nice helper function for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dif is bigger because we can use the helper function in other places, but I think this is a big improvement.

require.NoError(t, err, "EstimateGas()")
assert.Positive(t, got, "EstimateGas() result")

Check warning on line 284 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Positive`" continues on failure; consider require for fail-fast
})

t.Run("eth_createAccessList", func(t *testing.T) {
gc := gethclient.New(sut.rpcClient)
al, gas, errMsg, err := gc.CreateAccessList(ctx, callMsg)
require.NoError(t, err, "CreateAccessList()")
assert.Empty(t, errMsg, "CreateAccessList() error message")

Check warning on line 291 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Empty`" continues on failure; consider require for fail-fast
assert.NotEmpty(t, al, "CreateAccessList() access list")

Check warning on line 292 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.NotEmpty`" continues on failure; consider require for fail-fast
assert.Positive(t, gas, "CreateAccessList() gasUsed")

Check warning on line 293 in vms/saevm/sae/rpc_stateful_test.go

View workflow job for this annotation

GitHub Actions / Lint

`assert.Positive`" continues on failure; consider require for fail-fast
})
}
49 changes: 49 additions & 0 deletions vms/saevm/sae/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/ava-labs/libevm/core/txpool/legacypool"
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/core/vm"
"github.com/ava-labs/libevm/crypto"
"github.com/ava-labs/libevm/ethclient"
"github.com/ava-labs/libevm/ethdb"
"github.com/ava-labs/libevm/libevm"
Expand Down Expand Up @@ -54,11 +55,13 @@ import (
"github.com/ava-labs/avalanchego/vms/saevm/hook"
"github.com/ava-labs/avalanchego/vms/saevm/hook/hookstest"
"github.com/ava-labs/avalanchego/vms/saevm/saetest"
"github.com/ava-labs/avalanchego/vms/saevm/saetest/escrow"
"github.com/ava-labs/avalanchego/vms/saevm/txgossip/txgossiptest"

snowcommon "github.com/ava-labs/avalanchego/snow/engine/common"
saeparams "github.com/ava-labs/avalanchego/vms/saevm/params"
saetypes "github.com/ava-labs/avalanchego/vms/saevm/types"
ethereum "github.com/ava-labs/libevm"
libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest"
)

Expand Down Expand Up @@ -444,6 +447,52 @@ func (s *SUT) runConsensusLoop(tb testing.TB, txs ...*types.Transaction) *blocks
return s.runConsensusLoopOnPreference(tb, s.lastAcceptedBlock(tb), txs...)
}

// escrowDepositVal is deposit amount used by [SUT.deployEscrow] when the caller
// requests a deposit.
const escrowDepositVal = 42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be a global constant

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not for testing? You'd rather it be duplicated throughout the tests?


// deployEscrow signs and runs a deploy tx for the escrow contract from
// s.wallet[0]. If depositVal is non-nil, a tx depositing that value to
// balances[recv] is included in the same consensus block. All included txs
// are asserted to execute successfully.
func (s *SUT) deployEscrow(ctx context.Context, tb testing.TB, depositVal *big.Int) (
b *blocks.Block, escrowAddr, recv common.Address, callMsg ethereum.CallMsg,
) {
tb.Helper()
escrowAddr = crypto.CreateAddress(s.wallet.Addresses()[0], 0)
recv = common.Address{'r', 'e', 'c', 'v'}

sign := s.wallet.SetNonceAndSign
txs := []*types.Transaction{sign(tb, 0, &types.LegacyTx{
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CreationCode(),
})}
if depositVal != nil {
txs = append(txs, sign(tb, 0, &types.LegacyTx{
Comment on lines +466 to +472
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad news - this is racy in the legacypool if there's a block executing

To: &escrowAddr,
Gas: 1e6,
GasPrice: big.NewInt(1),
Data: escrow.CallDataToDeposit(recv),
Value: depositVal,
}))
}

b = s.runConsensusLoop(tb, txs...)
require.Lenf(tb, b.Transactions(), len(txs), "%T.Transactions()", b)
require.NoErrorf(tb, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b)
for _, r := range b.Receipts() {
require.Equalf(tb, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r)
}

callMsg = ethereum.CallMsg{
From: s.wallet.Addresses()[0],
To: &escrowAddr,
Data: escrow.CallDataForBalance(recv),
}
return b, escrowAddr, recv, callMsg
}

func (s *SUT) stateAt(tb testing.TB, root common.Hash) *state.StateDB {
tb.Helper()
sdb, err := s.rawVM.exec.StateDB(root)
Expand Down
Loading