Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Solana e2e deposit and call; deposit and revert #2726

Merged
merged 9 commits into from
Aug 19, 2024
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

* [2615](https://github.com/zeta-chain/node/pull/2615) - Refactor cleanup of outbound trackers

### Tests

* [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert

### Fixes

* [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module
Expand Down
2 changes: 2 additions & 0 deletions cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
solanaTests := []string{
e2etests.TestSolanaDepositName,
e2etests.TestSolanaWithdrawName,
e2etests.TestSolanaDepositAndCallName,
e2etests.TestSolanaDepositAndCallRefundName,
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...))
}
Expand Down
26 changes: 22 additions & 4 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ const (
/*
Solana tests
*/
TestSolanaDepositName = "solana_deposit"
TestSolanaWithdrawName = "solana_withdraw"
TestSolanaDepositName = "solana_deposit"
TestSolanaWithdrawName = "solana_withdraw"
TestSolanaDepositAndCallName = "solana_deposit_and_call"
TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund"

/*
Bitcoin tests
Expand Down Expand Up @@ -341,18 +343,34 @@ var AllE2ETests = []runner.E2ETest{
TestSolanaDepositName,
"deposit SOL into ZEVM",
[]runner.ArgDefinition{
{Description: "amount in lamport", DefaultValue: "13370000"},
{Description: "amount in lamport", DefaultValue: "1200000"},
},
TestSolanaDeposit,
),
runner.NewE2ETest(
TestSolanaWithdrawName,
"withdraw SOL from ZEVM",
[]runner.ArgDefinition{
{Description: "amount in lamport", DefaultValue: "1336000"},
{Description: "amount in lamport", DefaultValue: "1000000"},
},
TestSolanaWithdraw,
),
runner.NewE2ETest(
TestSolanaDepositAndCallName,
"deposit SOL into ZEVM and call a contract",
[]runner.ArgDefinition{
{Description: "amount in lamport", DefaultValue: "1200000"},
},
TestSolanaDepositAndCall,
),
runner.NewE2ETest(
TestSolanaDepositAndCallRefundName,
"deposit SOL into ZEVM and call a contract that reverts; should refund",
[]runner.ArgDefinition{
{Description: "amount in lamport", DefaultValue: "1200000"},
},
TestSolanaDepositAndCallRefund,
),
/*
Bitcoin tests
*/
Expand Down
15 changes: 2 additions & 13 deletions e2e/e2etests/test_eth_deposit_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package e2etests
import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -57,16 +56,7 @@ func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) {
utils.RequireCCTXStatus(r, cctx, types.CctxStatus_OutboundMined)

// Checking example contract has been called, bar value should be set to amount
bar, err := exampleContract.Bar(&bind.CallOpts{})
require.NoError(r, err)
require.Equal(
r,
0,
bar.Cmp(value),
"cross-chain call failed bar value %s should be equal to amount %s",
bar.String(),
value.String(),
)
utils.MustHaveCalledExampleContract(r, exampleContract, value)
r.Logger.Info("Cross-chain call succeeded")

r.Logger.Info("Deploying reverter contract")
Expand Down Expand Up @@ -100,6 +90,5 @@ func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) {
r.Logger.Info("Cross-chain call to reverter reverted")

// check the status message contains revert error hash in case of revert
// 0xbfb4ebcf is the hash of "Foo()"
require.Contains(r, cctx.CctxStatus.StatusMessage, "0xbfb4ebcf")
require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevert)
}
17 changes: 16 additions & 1 deletion e2e/e2etests/test_solana_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package e2etests
import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"

Expand All @@ -14,6 +15,11 @@ import (
func TestSolanaDeposit(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

// get ERC20 SOL balance before deposit
balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SOL before deposit: %d", balanceBefore)

// parse deposit amount (in lamports)
// #nosec G115 e2e - always in range
depositAmount := big.NewInt(int64(parseInt(r, args[0])))
Expand All @@ -23,7 +29,7 @@ func TestSolanaDeposit(r *runner.E2ERunner, args []string) {
require.NoError(r, err)

// create 'deposit' instruction
instruction := r.CreateDepositInstruction(privkey.PublicKey(), r.EVMAddress(), depositAmount.Uint64())
instruction := r.CreateDepositInstruction(privkey.PublicKey(), r.EVMAddress(), nil, depositAmount.Uint64())

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, privkey)
Expand All @@ -35,4 +41,13 @@ func TestSolanaDeposit(r *runner.E2ERunner, args []string) {
// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// get ERC20 SOL balance after deposit
balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SOL after deposit: %d", balanceAfter)

// the runner balance should be increased by the deposit amount
amountIncreased := new(big.Int).Sub(balanceAfter, balanceBefore)
require.Equal(r, depositAmount.String(), amountIncreased.String())
}
53 changes: 53 additions & 0 deletions e2e/e2etests/test_solana_deposit_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package e2etests

import (
"math/big"

"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"

"github.com/zeta-chain/zetacore/e2e/runner"
"github.com/zeta-chain/zetacore/e2e/utils"
testcontract "github.com/zeta-chain/zetacore/testutil/contracts"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
)

// TestSolanaDepositAndCall tests deposit of lamports calling a example contract
func TestSolanaDepositAndCall(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

// parse deposit amount (in lamports)
// #nosec G115 e2e - always in range
depositAmount := big.NewInt(int64(parseInt(r, args[0])))

// deploy an example contract in ZEVM
contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient)
require.NoError(r, err)

r.Logger.Info("Example contract deployed at: %s", contractAddr.String())

// ---------------------------------------- execute the deposit transaction ----------------------------------------
// load deployer private key
privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

// create 'deposit' instruction
data := []byte("hello lamports")
instruction := r.CreateDepositInstruction(privkey.PublicKey(), contractAddr, data, depositAmount.Uint64())

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, privkey)

// broadcast the transaction and wait for finalization
sig, out := r.BroadcastTxSync(signedTx)
r.Logger.Info("deposit logs: %v", out.Meta.LogMessages)

// ---------------------------------------- verify the cross-chain call --------------------------------------------
// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// check if example contract has been called, bar value should be set to amount
utils.MustHaveCalledExampleContract(r, contract, depositAmount)
r.Logger.Info("cross-chain call succeeded")
}
54 changes: 54 additions & 0 deletions e2e/e2etests/test_solana_deposit_refund.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package e2etests

import (
"math/big"

"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"

"github.com/zeta-chain/zetacore/e2e/runner"
"github.com/zeta-chain/zetacore/e2e/utils"
testcontract "github.com/zeta-chain/zetacore/testutil/contracts"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
)

// TestSolanaDepositAndCallRefund tests deposit of lamports calling a example contract
func TestSolanaDepositAndCallRefund(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

// parse deposit amount (in lamports)
// #nosec G115 e2e - always in range
depositAmount := big.NewInt(int64(parseInt(r, args[0])))

// deploy a reverter contract in ZEVM
r.Logger.Info("Deploying reverter contract")
reverterAddr, _, _, err := testcontract.DeployReverter(r.ZEVMAuth, r.ZEVMClient)
require.NoError(r, err)

r.Logger.Info("Reverter contract deployed at: %s", reverterAddr.String())

// ---------------------------------------- execute the deposit transaction ----------------------------------------
// load deployer private key
privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

// create 'deposit' instruction
data := []byte("hello reverter")
instruction := r.CreateDepositInstruction(privkey.PublicKey(), reverterAddr, data, depositAmount.Uint64())

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, privkey)

// broadcast the transaction and wait for finalization
sig, out := r.BroadcastTxSync(signedTx)
r.Logger.Info("deposit logs: %v", out.Meta.LogMessages)

// ---------------------------------------- verify the cross-chain revert --------------------------------------------
// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted)
r.Logger.Info("cross-chain call reverted: %v", cctx.CctxStatus.StatusMessage)

// check the status message contains revert error hash in case of revert
require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevert)
}
12 changes: 6 additions & 6 deletions e2e/e2etests/test_solana_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (
func TestSolanaWithdraw(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

// print balanceAfter of from address
balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From)
// get ERC20 SOL balance before withdraw
balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("from address %s balance of SOL before: %d", r.ZEVMAuth.From, balanceBefore)
r.Logger.Info("runner balance of SOL before withdraw: %d", balanceBefore)

// parse withdraw amount (in lamports), approve amount is 1 SOL
approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL)
Expand All @@ -36,10 +36,10 @@ func TestSolanaWithdraw(r *runner.E2ERunner, args []string) {
// withdraw
r.WithdrawSOLZRC20(privkey.PublicKey(), withdrawAmount, approvedAmount)

// print balance of from address after withdraw
balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From)
// get ERC20 SOL balance after withdraw
balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("from address %s balance of SOL after: %d", r.ZEVMAuth.From, balanceAfter)
r.Logger.Info("runner balance of SOL after withdraw: %d", balanceAfter)

// check if the balance is reduced correctly
amountReduced := new(big.Int).Sub(balanceBefore, balanceAfter)
Expand Down
10 changes: 9 additions & 1 deletion e2e/runner/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (r *E2ERunner) ComputePdaAddress() solana.PublicKey {
func (r *E2ERunner) CreateDepositInstruction(
signer solana.PublicKey,
receiver ethcommon.Address,
data []byte,
amount uint64,
) solana.Instruction {
// compute the gateway PDA address
Expand All @@ -51,7 +52,7 @@ func (r *E2ERunner) CreateDepositInstruction(
inst.DataBytes, err = borsh.Serialize(solanacontract.DepositInstructionParams{
Discriminator: solanacontract.DiscriminatorDeposit(),
Amount: amount,
Memo: receiver.Bytes(),
Memo: append(receiver.Bytes(), data...),
})
require.NoError(r, err)

Expand Down Expand Up @@ -96,9 +97,16 @@ func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, *
require.NoError(r, err)
r.Logger.Info("broadcast success! tx sig %s; waiting for confirmation...", sig)

var (
start = time.Now()
timeout = 2 * time.Minute // Solana tx expires automatically after 2 minutes
)

// wait for the transaction to be finalized
var out *rpc.GetTransactionResult
for {
require.False(r, time.Since(start) > timeout, "waiting solana tx timeout")

time.Sleep(1 * time.Second)
out, err = r.SolanaClient.GetTransaction(r.Ctx, sig, &rpc.GetTransactionOpts{})
if err == nil {
Expand Down
33 changes: 33 additions & 0 deletions e2e/utils/contracts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package utils

import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"

testcontract "github.com/zeta-chain/zetacore/testutil/contracts"
)

const (
// ErrHashRevert is the keccak256 hash of custom error "Foo()" on reverter contract
ErrHashRevert = "0xbfb4ebcf"
)

// MustHaveCalledExampleContract checks if the contract has been called correctly
func MustHaveCalledExampleContract(
t require.TestingT,
contract *testcontract.Example,
amount *big.Int,
) {
bar, err := contract.Bar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(
t,
0,
bar.Cmp(amount),
"cross-chain call failed bar value %s should be equal to amount %s",
bar.String(),
amount.String(),
)
}
5 changes: 3 additions & 2 deletions x/fungible/keeper/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

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

Expand All @@ -16,6 +17,7 @@ import (
"github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol"
zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol"

"github.com/zeta-chain/zetacore/e2e/utils"
"github.com/zeta-chain/zetacore/pkg/chains"
"github.com/zeta-chain/zetacore/pkg/coin"
"github.com/zeta-chain/zetacore/server/config"
Expand Down Expand Up @@ -574,8 +576,7 @@ func TestKeeper_CallEVMWithData(t *testing.T) {
require.True(t, types.IsContractReverted(res, err))

// check reason is included for revert error
// 0xbfb4ebcf is the hash of "Foo()"
require.ErrorContains(t, err, "reason: 0xbfb4ebcf")
require.ErrorContains(t, err, fmt.Sprintf("reason: %s", utils.ErrHashRevert))

res, err = k.CallEVM(
ctx,
Expand Down
Loading