Skip to content

Commit

Permalink
feat: integrate SPL deposits (#3124)
Browse files Browse the repository at this point in the history
* deposit spl integration and start with e2e test

* test wip for deposit spl and call

* fix up creation of ata

* add balance assertions for deposit spl e2e tests

* CI fixes

* lint fix

* add inbound parse unit test and cleanup

* comment

* PR comments

* move inbound parsing to solana pkg

* refactor e2e test zrc20 deployment

* fix e2e tests

* Update changelog.md

Co-authored-by: Lucas Bertrand <[email protected]>

---------

Co-authored-by: Dmitry S <[email protected]>
Co-authored-by: Lucas Bertrand <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent 1f2c1e1 commit 13cfffe
Show file tree
Hide file tree
Showing 25 changed files with 907 additions and 226 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features
* [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana
* [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable.
* [3124](https://github.com/zeta-chain/node/pull/3124) - integrate SPL deposits

### Tests
* [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert.
Expand Down
4 changes: 3 additions & 1 deletion cmd/zetae2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ func RunnerFromConfig(

// ExportContractsFromRunner export contracts from the runner to config using a source config
func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.Config {
// copy contracts from deployer runner
conf.Contracts.Solana.GatewayProgramID = r.GatewayProgram.String()
conf.Contracts.Solana.SPLAddr = config.DoubleQuotedString(r.SPLAddr.String())

// copy contracts from deployer runner
conf.Contracts.EVM.ZetaEthAddr = config.DoubleQuotedString(r.ZetaEthAddr.Hex())
conf.Contracts.EVM.ConnectorEthAddr = config.DoubleQuotedString(r.ConnectorEthAddr.Hex())
conf.Contracts.EVM.CustodyAddr = config.DoubleQuotedString(r.ERC20CustodyAddr.Hex())
Expand All @@ -68,6 +69,7 @@ func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.C
conf.Contracts.ZEVM.ERC20ZRC20Addr = config.DoubleQuotedString(r.ERC20ZRC20Addr.Hex())
conf.Contracts.ZEVM.BTCZRC20Addr = config.DoubleQuotedString(r.BTCZRC20Addr.Hex())
conf.Contracts.ZEVM.SOLZRC20Addr = config.DoubleQuotedString(r.SOLZRC20Addr.Hex())
conf.Contracts.ZEVM.SPLZRC20Addr = config.DoubleQuotedString(r.SPLZRC20Addr.Hex())
conf.Contracts.ZEVM.TONZRC20Addr = config.DoubleQuotedString(r.TONZRC20Addr.Hex())
conf.Contracts.ZEVM.UniswapFactoryAddr = config.DoubleQuotedString(r.UniswapV2FactoryAddr.Hex())
conf.Contracts.ZEVM.UniswapRouterAddr = config.DoubleQuotedString(r.UniswapV2RouterAddr.Hex())
Expand Down
15 changes: 15 additions & 0 deletions cmd/zetae2e/config/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error {
r.GatewayProgram = solana.MustPublicKeyFromBase58(c)
}

if c := conf.Contracts.Solana.SPLAddr; c != "" {
r.SPLAddr = solana.MustPublicKeyFromBase58(c.String())
}

// set EVM contracts
if c := conf.Contracts.EVM.ZetaEthAddr; c != "" {
r.ZetaEthAddr, err = c.AsEVMAddress()
Expand Down Expand Up @@ -135,6 +139,17 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error {
}
}

if c := conf.Contracts.ZEVM.SPLZRC20Addr; c != "" {
r.SPLZRC20Addr, err = c.AsEVMAddress()
if err != nil {
return fmt.Errorf("invalid SPLZRC20Addr: %w", err)
}
r.SPLZRC20, err = zrc20.NewZRC20(r.SPLZRC20Addr, r.ZEVMClient)
if err != nil {
return err
}
}

if c := conf.Contracts.ZEVM.TONZRC20Addr; c != "" {
r.TONZRC20Addr, err = c.AsEVMAddress()
if err != nil {
Expand Down
20 changes: 15 additions & 5 deletions cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,31 @@ func localE2ETest(cmd *cobra.Command, _ []string) {

deployerRunner.SetupEVMV2()

if testSolana {
deployerRunner.SetupSolana(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String())
}

deployerRunner.SetZEVMSystemContracts()

// NOTE: v2 (gateway) setup called here because system contract needs to be set first, then gateway, then zrc20
deployerRunner.SetZEVMContractsV2()

deployerRunner.SetZEVMZRC20s()
zrc20Deployment := txserver.ZRC20Deployment{
ERC20Addr: deployerRunner.ERC20Addr,
SPLAddr: nil,
}
if testSolana {
zrc20Deployment.SPLAddr = deployerRunner.SPLAddr.ToPointer()
}

deployerRunner.SetZEVMZRC20s(zrc20Deployment)

// Update the chain params to use v2 contract for ERC20Custody
// TODO: this function should be removed and the chain params should be directly set to use v2 contract
// https://github.com/zeta-chain/node/issues/2627
deployerRunner.UpdateChainParamsV2Contracts()
deployerRunner.ERC20CustodyAddr = deployerRunner.ERC20CustodyV2Addr

if testSolana {
deployerRunner.SetupSolana(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String())
}

deployerRunner.MintERC20OnEvm(1000000)

logger.Print("✅ setup completed in %s", time.Since(startTime))
Expand Down Expand Up @@ -417,6 +425,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
// TODO move under admin tests
// https://github.com/zeta-chain/node/issues/3085
e2etests.TestSolanaWhitelistSPLName,
e2etests.TestSPLDepositName,
e2etests.TestSPLDepositAndCallName,
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...))
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/zetae2e/stress.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
zetae2econfig "github.com/zeta-chain/node/cmd/zetae2e/config"
"github.com/zeta-chain/node/cmd/zetae2e/local"
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/txserver"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/testutil"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
Expand Down Expand Up @@ -142,7 +143,10 @@ func StressTest(cmd *cobra.Command, _ []string) {
case "LOCAL":
// deploy and set zevm contract
e2eTest.SetZEVMSystemContracts()
e2eTest.SetZEVMZRC20s()
e2eTest.SetZEVMZRC20s(txserver.ZRC20Deployment{
ERC20Addr: e2eTest.ERC20Addr,
SPLAddr: nil, // no stress tests for solana atm
})

// deposit on ZetaChain
e2eTest.DepositEther()
Expand Down
6 changes: 4 additions & 2 deletions e2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ type Contracts struct {
Solana Solana `yaml:"solana"`
}

// Solana contains the addresses of predeployed contracts on the Solana chain
// Solana contains the addresses of predeployed contracts and accounts on the Solana chain
type Solana struct {
GatewayProgramID string `yaml:"gateway_program_id"`
GatewayProgramID string `yaml:"gateway_program_id"`
SPLAddr DoubleQuotedString `yaml:"spl"`
}

// EVM contains the addresses of predeployed contracts on the EVM chain
Expand All @@ -141,6 +142,7 @@ type ZEVM struct {
ERC20ZRC20Addr DoubleQuotedString `yaml:"erc20_zrc20"`
BTCZRC20Addr DoubleQuotedString `yaml:"btc_zrc20"`
SOLZRC20Addr DoubleQuotedString `yaml:"sol_zrc20"`
SPLZRC20Addr DoubleQuotedString `yaml:"spl_zrc20"`
TONZRC20Addr DoubleQuotedString `yaml:"ton_zrc20"`
UniswapFactoryAddr DoubleQuotedString `yaml:"uniswap_factory"`
UniswapRouterAddr DoubleQuotedString `yaml:"uniswap_router"`
Expand Down
18 changes: 18 additions & 0 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const (
TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund"
TestSolanaDepositRestrictedName = "solana_deposit_restricted"
TestSolanaWithdrawRestrictedName = "solana_withdraw_restricted"
TestSPLDepositName = "spl_deposit"
TestSPLDepositAndCallName = "spl_deposit_and_call"

/**
* TON tests
Expand Down Expand Up @@ -463,6 +465,22 @@ var AllE2ETests = []runner.E2ETest{
[]runner.ArgDefinition{},
TestSolanaWhitelistSPL,
),
runner.NewE2ETest(
TestSPLDepositName,
"deposit SPL into ZEVM",
[]runner.ArgDefinition{
{Description: "amount of spl tokens", DefaultValue: "500000"},
},
TestSPLDeposit,
),
runner.NewE2ETest(
TestSPLDepositAndCallName,
"deposit SPL into ZEVM and call",
[]runner.ArgDefinition{
{Description: "amount of spl tokens", DefaultValue: "500000"},
},
TestSPLDepositAndCall,
),
/*
TON tests
*/
Expand Down
3 changes: 2 additions & 1 deletion e2e/e2etests/test_solana_whitelist_spl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ func TestSolanaWhitelistSPL(r *runner.E2ERunner, _ []string) {
privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

spl := r.DeploySPL(&privkey)
// deploy SPL token, but don't whitelist in gateway
spl := r.DeploySPL(&privkey, false)

// check that whitelist entry doesn't exist for this spl
seed := [][]byte{[]byte("whitelist"), spl.PublicKey().Bytes()}
Expand Down
66 changes: 66 additions & 0 deletions e2e/e2etests/test_spl_deposit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package e2etests

import (
"math/big"

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

"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
)

func TestSPLDeposit(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)
amount := parseInt(r, args[0])

// load deployer private key
privKey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

// get SPL balance for pda and sender atas
pda := r.ComputePdaAddress()
pdaAta := r.FindOrCreateAssociatedTokenAccount(privKey, pda, r.SPLAddr)

pdaBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

senderAta := r.FindOrCreateAssociatedTokenAccount(privKey, privKey.PublicKey(), r.SPLAddr)
senderBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

// get zrc20 balance for recipient
zrc20BalanceBefore, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)

// deposit SPL tokens
// #nosec G115 e2eTest - always in range
sig := r.SPLDepositAndCall(&privKey, uint64(amount), r.SPLAddr, r.EVMAddress(), nil)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "solana_deposit_spl")
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// verify balances are updated
pdaBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

senderBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

zrc20BalanceAfter, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)

// verify amount is deposited to pda ata
require.Equal(r, parseInt(r, pdaBalanceBefore.Value.Amount)+amount, parseInt(r, pdaBalanceAfter.Value.Amount))

// verify amount is subtracted from sender ata
require.Equal(r, parseInt(r, senderBalanceBefore.Value.Amount)-amount, parseInt(r, senderBalanceAfter.Value.Amount))

// verify amount is minted to receiver
require.Zero(r, zrc20BalanceBefore.Add(zrc20BalanceBefore, big.NewInt(int64(amount))).Cmp(zrc20BalanceAfter))
}
76 changes: 76 additions & 0 deletions e2e/e2etests/test_spl_deposit_and_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package e2etests

import (
"math/big"

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

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

func TestSPLDepositAndCall(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)
amount := 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())

// load deployer private key
privKey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

// get SPL balance for pda and sender atas
pda := r.ComputePdaAddress()
pdaAta := r.FindOrCreateAssociatedTokenAccount(privKey, pda, r.SPLAddr)

pdaBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

senderAta := r.FindOrCreateAssociatedTokenAccount(privKey, privKey.PublicKey(), r.SPLAddr)
senderBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

// get zrc20 balance for recipient
zrc20BalanceBefore, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, contractAddr)
require.NoError(r, err)

// execute the deposit transaction
data := []byte("hello spl tokens")
// #nosec G115 e2eTest - always in range
sig := r.SPLDepositAndCall(&privKey, uint64(amount), r.SPLAddr, contractAddr, data)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "solana_deposit_spl_and_call")
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, big.NewInt(int64(amount)))

// verify balances are updated
pdaBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

senderBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

zrc20BalanceAfter, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, contractAddr)
require.NoError(r, err)

// verify amount is deposited to pda ata
require.Equal(r, parseInt(r, pdaBalanceBefore.Value.Amount)+amount, parseInt(r, pdaBalanceAfter.Value.Amount))

// verify amount is subtracted from sender ata
require.Equal(r, parseInt(r, senderBalanceBefore.Value.Amount)-amount, parseInt(r, senderBalanceAfter.Value.Amount))

// verify amount is minted to receiver
require.Zero(r, zrc20BalanceBefore.Add(zrc20BalanceBefore, big.NewInt(int64(amount))).Cmp(zrc20BalanceAfter))
}
6 changes: 6 additions & 0 deletions e2e/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type E2ERunner struct {

// programs on Solana
GatewayProgram solana.PublicKey
SPLAddr solana.PublicKey

// contracts evm
ZetaEthAddr ethcommon.Address
Expand All @@ -125,6 +126,8 @@ type E2ERunner struct {
// contracts zevm
ERC20ZRC20Addr ethcommon.Address
ERC20ZRC20 *zrc20.ZRC20
SPLZRC20Addr ethcommon.Address
SPLZRC20 *zrc20.ZRC20
ETHZRC20Addr ethcommon.Address
ETHZRC20 *zrc20.ZRC20
BTCZRC20Addr ethcommon.Address
Expand Down Expand Up @@ -366,13 +369,16 @@ func (r *E2ERunner) Unlock() {
func (r *E2ERunner) PrintContractAddresses() {
r.Logger.Print(" --- 📜Solana addresses ---")
r.Logger.Print("GatewayProgram: %s", r.GatewayProgram.String())
r.Logger.Print("SPL: %s", r.SPLAddr.String())

// zevm contracts
r.Logger.Print(" --- 📜zEVM contracts ---")
r.Logger.Print("SystemContract: %s", r.SystemContractAddr.Hex())
r.Logger.Print("ETHZRC20: %s", r.ETHZRC20Addr.Hex())
r.Logger.Print("ERC20ZRC20: %s", r.ERC20ZRC20Addr.Hex())
r.Logger.Print("BTCZRC20: %s", r.BTCZRC20Addr.Hex())
r.Logger.Print("SOLZRC20: %s", r.SOLZRC20Addr.Hex())
r.Logger.Print("SPLZRC20: %s", r.SPLZRC20Addr.Hex())
r.Logger.Print("TONZRC20: %s", r.TONZRC20Addr.Hex())
r.Logger.Print("UniswapFactory: %s", r.UniswapV2FactoryAddr.Hex())
r.Logger.Print("UniswapRouter: %s", r.UniswapV2RouterAddr.Hex())
Expand Down
4 changes: 4 additions & 0 deletions e2e/runner/setup_solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ func (r *E2ERunner) SetupSolana(deployerPrivateKey string) {

err = r.ensureSolanaChainParams()
require.NoError(r, err)

// deploy test spl
tokenAccount := r.DeploySPL(&privkey, true)
r.SPLAddr = tokenAccount.PublicKey()
}

func (r *E2ERunner) ensureSolanaChainParams() error {
Expand Down
Loading

0 comments on commit 13cfffe

Please sign in to comment.