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 withdraw SPL #3134

Merged
merged 14 commits into from
Nov 12, 2024
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* [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
* [3134](https://github.com/zeta-chain/node/pull/3134) - integrate SPL tokens withdraw to Solana

### Tests

Expand Down
4 changes: 3 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
e2etests.TestSolanaWithdrawRestrictedName,
// TODO move under admin tests
// https://github.com/zeta-chain/node/issues/3085
e2etests.TestSolanaWhitelistSPLName,
e2etests.TestSPLDepositName,
e2etests.TestSPLDepositAndCallName,
e2etests.TestSPLWithdrawName,
e2etests.TestSPLWithdrawAndCreateReceiverAtaName,
e2etests.TestSolanaWhitelistSPLName,
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...))
}
Expand Down
3 changes: 2 additions & 1 deletion contrib/localnet/solana/start-solana.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ echo "starting solana test validator..."
solana-test-validator &

sleep 5
# airdrop to e2e sol account
# airdrop to e2e sol account and rent payer (used to generate atas for withdraw spl receivers if they don't exist)
solana airdrop 100
solana airdrop 100 37yGiHAnLvWZUNVwu9esp74YQFqxU1qHCbABkDvRddUQ
solana airdrop 100 C6KPvGDYfNusoE4yfRP21F8wK35bxCBMT69xk4xo3X79
skosito marked this conversation as resolved.
Show resolved Hide resolved
solana program deploy gateway.so


Expand Down
38 changes: 28 additions & 10 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ const (
/*
* Solana tests
*/
TestSolanaDepositName = "solana_deposit"
TestSolanaWithdrawName = "solana_withdraw"
TestSolanaDepositAndCallName = "solana_deposit_and_call"
TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund"
TestSolanaDepositRestrictedName = "solana_deposit_restricted"
TestSolanaWithdrawRestrictedName = "solana_withdraw_restricted"
TestSPLDepositName = "spl_deposit"
TestSPLDepositAndCallName = "spl_deposit_and_call"
TestSolanaDepositName = "solana_deposit"
TestSolanaWithdrawName = "solana_withdraw"
TestSolanaDepositAndCallName = "solana_deposit_and_call"
TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund"
TestSolanaDepositRestrictedName = "solana_deposit_restricted"
TestSolanaWithdrawRestrictedName = "solana_withdraw_restricted"
TestSPLDepositName = "spl_deposit"
TestSPLDepositAndCallName = "spl_deposit_and_call"
TestSPLWithdrawName = "spl_withdraw"
TestSPLWithdrawAndCreateReceiverAtaName = "spl_withdraw_and_create_receiver_ata"

/**
* TON tests
Expand Down Expand Up @@ -434,6 +436,22 @@ var AllE2ETests = []runner.E2ETest{
},
TestSolanaDepositAndCall,
),
runner.NewE2ETest(
TestSPLWithdrawName,
"withdraw SPL from ZEVM",
[]runner.ArgDefinition{
{Description: "amount in spl tokens", DefaultValue: "1000000"},
},
TestSPLWithdraw,
),
runner.NewE2ETest(
TestSPLWithdrawAndCreateReceiverAtaName,
"withdraw SPL from ZEVM and create receiver ata",
[]runner.ArgDefinition{
{Description: "amount in spl tokens", DefaultValue: "1000000"},
},
TestSPLWithdrawAndCreateReceiverAta,
),
runner.NewE2ETest(
TestSolanaDepositAndCallRefundName,
"deposit SOL into ZEVM and call a contract that reverts; should refund",
Expand Down Expand Up @@ -470,15 +488,15 @@ var AllE2ETests = []runner.E2ETest{
TestSPLDepositName,
"deposit SPL into ZEVM",
[]runner.ArgDefinition{
{Description: "amount of spl tokens", DefaultValue: "500000"},
{Description: "amount of spl tokens", DefaultValue: "12000000"},
},
TestSPLDeposit,
),
runner.NewE2ETest(
TestSPLDepositAndCallName,
"deposit SPL into ZEVM and call",
[]runner.ArgDefinition{
{Description: "amount of spl tokens", DefaultValue: "500000"},
{Description: "amount of spl tokens", DefaultValue: "12000000"},
},
TestSPLDepositAndCall,
),
Expand Down
3 changes: 1 addition & 2 deletions e2e/e2etests/test_solana_whitelist_spl.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ func TestSolanaWhitelistSPL(r *runner.E2ERunner, _ []string) {
r.Logger.Info("Deploying new SPL")

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

// deploy SPL token, but don't whitelist in gateway
spl := r.DeploySPL(&privkey, false)
Expand Down
14 changes: 10 additions & 4 deletions e2e/e2etests/test_solana_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"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 TestSolanaWithdraw(r *runner.E2ERunner, args []string) {
Expand All @@ -28,15 +30,19 @@ func TestSolanaWithdraw(r *runner.E2ERunner, args []string) {
r,
-1,
withdrawAmount.Cmp(approvedAmount),
"Withdrawal amount must be less than the approved amount (1e9)",
"Withdrawal amount must be less than the approved amount: %v",
approvedAmount,
)

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

// withdraw
r.WithdrawSOLZRC20(privkey.PublicKey(), withdrawAmount, approvedAmount)
tx := r.WithdrawSOLZRC20(privkey.PublicKey(), withdrawAmount, approvedAmount)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// get ERC20 SOL balance after withdraw
balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
Expand Down
8 changes: 7 additions & 1 deletion e2e/e2etests/test_solana_withdraw_restricted_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"github.com/stretchr/testify/require"

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

func TestSolanaWithdrawRestricted(r *runner.E2ERunner, args []string) {
Expand All @@ -29,7 +31,11 @@ func TestSolanaWithdrawRestricted(r *runner.E2ERunner, args []string) {
)

// withdraw
cctx := r.WithdrawSOLZRC20(receiverRestricted, withdrawAmount, approvedAmount)
tx := r.WithdrawSOLZRC20(receiverRestricted, withdrawAmount, approvedAmount)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// the cctx should be cancelled with zero value
verifySolanaWithdrawalAmountFromCCTX(r, cctx, 0)
Expand Down
8 changes: 3 additions & 5 deletions e2e/e2etests/test_spl_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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"

Expand All @@ -18,17 +17,16 @@ func TestSPLDeposit(r *runner.E2ERunner, args []string) {
amount := parseInt(r, args[0])

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

// get SPL balance for pda and sender atas
pda := r.ComputePdaAddress()
pdaAta := r.FindOrCreateAssociatedTokenAccount(privKey, pda, r.SPLAddr)
pdaAta := r.FindOrCreateAta(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)
senderAta := r.FindOrCreateAta(privKey, privKey.PublicKey(), r.SPLAddr)
senderBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

Expand Down
8 changes: 3 additions & 5 deletions e2e/e2etests/test_spl_deposit_and_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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"

Expand All @@ -24,17 +23,16 @@ func TestSPLDepositAndCall(r *runner.E2ERunner, args []string) {
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)
privKey := r.GetSolanaPrivKey()

// get SPL balance for pda and sender atas
pda := r.ComputePdaAddress()
pdaAta := r.FindOrCreateAssociatedTokenAccount(privKey, pda, r.SPLAddr)
pdaAta := r.FindOrCreateAta(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)
senderAta := r.FindOrCreateAta(privKey, privKey.PublicKey(), r.SPLAddr)
senderBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, senderAta, rpc.CommitmentConfirmed)
require.NoError(r, err)

Expand Down
69 changes: 69 additions & 0 deletions e2e/e2etests/test_spl_withdraw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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 TestSPLWithdraw(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

withdrawAmount := parseBigInt(r, args[0])

skosito marked this conversation as resolved.
Show resolved Hide resolved
// get SPL ZRC20 balance before withdraw
zrc20BalanceBefore, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SPL before withdraw: %d", zrc20BalanceBefore)

require.Equal(r, 1, zrc20BalanceBefore.Cmp(withdrawAmount), "Insufficient balance for withdrawal")

// parse withdraw amount (in lamports), approve amount is 1 SOL
approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL)
require.Equal(
r,
-1,
withdrawAmount.Cmp(approvedAmount),
"Withdrawal amount must be less than the %v",
approvedAmount,
)

// load deployer private key
privkey := r.GetSolanaPrivKey()

// get receiver ata balance before withdraw
receiverAta := r.FindOrCreateAta(privkey, privkey.PublicKey(), r.SPLAddr)
receiverBalanceBefore, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, receiverAta, rpc.CommitmentConfirmed)
skosito marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(r, err)
r.Logger.Info("receiver balance of SPL before withdraw: %s", receiverBalanceBefore.Value.Amount)

// withdraw
tx := r.WithdrawSPLZRC20(privkey.PublicKey(), withdrawAmount, approvedAmount)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// get SPL ZRC20 balance after withdraw
zrc20BalanceAfter, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SPL after withdraw: %d", zrc20BalanceAfter)

// verify balances are updated
receiverBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, receiverAta, rpc.CommitmentConfirmed)
skosito marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(r, err)
r.Logger.Info("receiver balance of SPL after withdraw: %s", receiverBalanceAfter.Value.Amount)

// verify amount is added to receiver ata
require.EqualValues(r, new(big.Int).Add(withdrawAmount, parseBigInt(r, receiverBalanceBefore.Value.Amount)).String(), parseBigInt(r, receiverBalanceAfter.Value.Amount).String())

// verify amount is subtracted on zrc20
require.EqualValues(r, new(big.Int).Sub(zrc20BalanceBefore, withdrawAmount).String(), zrc20BalanceAfter.String())
}
79 changes: 79 additions & 0 deletions e2e/e2etests/test_spl_withdraw_and_create_receiver_ata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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"
)

// TestSPLWithdrawAndCreateReceiverAta withdraws spl, but letting gateway to create receiver ata using rent payer
// instead of providing receiver that has it already created
func TestSPLWithdrawAndCreateReceiverAta(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

withdrawAmount := parseBigInt(r, args[0])

// get SPL ZRC20 balance before withdraw
zrc20BalanceBefore, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SPL before withdraw: %d", zrc20BalanceBefore)

require.Equal(r, 1, zrc20BalanceBefore.Cmp(withdrawAmount), "Insufficient balance for withdrawal")

// parse withdraw amount (in lamports), approve amount is 1 SOL
approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL)
require.Equal(
r,
-1,
withdrawAmount.Cmp(approvedAmount),
"Withdrawal amount must be less than the %v",
approvedAmount,
)

// create new priv key, with empty ata
receiverPrivKey, err := solana.NewRandomPrivateKey()
require.NoError(r, err)

// verify receiver ata account doesn't exist
receiverAta, _, err := solana.FindAssociatedTokenAddress(receiverPrivKey.PublicKey(), r.SPLAddr)
require.NoError(r, err)

receiverAtaAcc, err := r.SolanaClient.GetAccountInfo(r.Ctx, receiverAta)
require.Error(r, err)
require.Nil(r, receiverAtaAcc)
skosito marked this conversation as resolved.
Show resolved Hide resolved

// withdraw
tx := r.WithdrawSPLZRC20(receiverPrivKey.PublicKey(), withdrawAmount, approvedAmount)

// wait for the cctx to be mined
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// get SPL ZRC20 balance after withdraw
zrc20BalanceAfter, err := r.SPLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress())
require.NoError(r, err)
r.Logger.Info("runner balance of SPL after withdraw: %d", zrc20BalanceAfter)

// verify receiver ata was created
receiverAtaAcc, err = r.SolanaClient.GetAccountInfo(r.Ctx, receiverAta)
require.NoError(r, err)
require.NotNil(r, receiverAtaAcc)

// verify balances are updated
receiverBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, receiverAta, rpc.CommitmentConfirmed)
Copy link
Contributor

Choose a reason for hiding this comment

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

same question

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was fixed in this PR

require.NoError(r, err)
r.Logger.Info("receiver balance of SPL after withdraw: %s", receiverBalanceAfter.Value.Amount)

// verify amount is added to receiver ata
require.EqualValues(r, withdrawAmount.String(), parseBigInt(r, receiverBalanceAfter.Value.Amount).String())

// verify amount is subtracted on zrc20
require.EqualValues(r, new(big.Int).Sub(zrc20BalanceBefore, withdrawAmount).String(), zrc20BalanceAfter.String())
}
7 changes: 7 additions & 0 deletions e2e/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol"
zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol"
zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol"
Expand Down Expand Up @@ -434,3 +435,9 @@ func (r *E2ERunner) requireTxSuccessful(receipt *ethtypes.Receipt, msgAndArgs ..
func (r *E2ERunner) EVMAddress() ethcommon.Address {
return r.Account.EVMAddress()
}

func (r *E2ERunner) GetSolanaPrivKey() solana.PrivateKey {
privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)
return privkey
}
Loading
Loading