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

feat: integrate SPL deposits #3124

Merged
merged 16 commits into from
Nov 8, 2024
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) - deposit spl integration
skosito marked this conversation as resolved.
Show resolved Hide resolved

### Refactor
* [3122](https://github.com/zeta-chain/node/pull/3122) - improve & refactor zetaclientd cli
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 @@ -218,23 +218,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 @@ -413,6 +421,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 @@ -462,6 +464,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
skosito marked this conversation as resolved.
Show resolved Hide resolved

// 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
Loading