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: withdraw SOL from ZEVM to Solana #2560

Merged
merged 21 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
51516dc
port Panruo's outbound code and make compile pass
ws4charlie Jul 22, 2024
e3460c1
make SOL withdraw e2e test passing
ws4charlie Jul 23, 2024
3b8a3b6
Merge branch 'develop' of https://github.com/zeta-chain/node into sol…
ws4charlie Jul 24, 2024
74840ab
make solana outbound tracker goroutine working
ws4charlie Jul 25, 2024
0d1a8ef
allow solana gateway address to update
ws4charlie Jul 25, 2024
a7c95e5
integrate sub methods of SignMsgWithdraw and SignWithdrawTx
ws4charlie Jul 25, 2024
6ba16aa
initiate solana outbound tracker reporter
ws4charlie Jul 26, 2024
eb87d3b
implemented solana outbound tx verification
ws4charlie Jul 30, 2024
ef58feb
use the amount in tx result for outbound vote
ws4charlie Jul 30, 2024
c0a4df4
post Solana priority fee to zetacore
ws4charlie Jul 31, 2024
af41255
config Solana fee payer private key
ws4charlie Jul 31, 2024
e3471e7
Merge branch 'develop' of https://github.com/zeta-chain/node into sol…
ws4charlie Jul 31, 2024
e88593e
resolve 1st wave of comments in PR review
ws4charlie Jul 31, 2024
e94fd92
resolve 2nd wave of comments
ws4charlie Aug 1, 2024
d40364f
refactor IsOutboundProcessed as VoteOutboundIfConfirmed; move outboun…
ws4charlie Aug 1, 2024
ada3654
resolve 3rd wave of PR feedback
ws4charlie Aug 1, 2024
fc0c9bf
added description to explain what do we do about the outbound tracker…
ws4charlie Aug 1, 2024
805df92
Merge branch 'develop' into solana-outbound-SOL
ws4charlie Aug 1, 2024
a5e6e70
add additional error message; add additional method comment
ws4charlie Aug 1, 2024
d37ea07
fix gosec err
ws4charlie Aug 1, 2024
134238a
replace contex.TODO() with context.Background()
ws4charlie Aug 1, 2024
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
3 changes: 2 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
* [2518](https://github.com/zeta-chain/node/pull/2518) - add support for Solana address in zetacore
* [2483](https://github.com/zeta-chain/node/pull/2483) - add priorityFee (gasTipCap) gas to the state
* [2567](https://github.com/zeta-chain/node/pull/2567) - add sign latency metric to zetaclient (zetaclient_sign_latency)
* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing
* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing
* [2560](https://github.com/zeta-chain/node/pull/2560) - add support for Solana SOL token withdraw

### Refactor

Expand Down
14 changes: 13 additions & 1 deletion cmd/zetaclientd/init.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"path"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -36,6 +38,7 @@ type initArguments struct {
KeyringBackend string
HsmMode bool
HsmHotKey string
SolanaKey string
}

func init() {
Expand Down Expand Up @@ -69,6 +72,7 @@ func init() {
InitCmd.Flags().BoolVar(&initArgs.HsmMode, "hsm-mode", false, "enable hsm signer, default disabled")
InitCmd.Flags().
StringVar(&initArgs.HsmHotKey, "hsm-hotkey", "hsm-hotkey", "name of hotkey associated with hardware security module")
InitCmd.Flags().StringVar(&initArgs.SolanaKey, "solana-key", "solana-key.json", "solana key file name")
}

func Initialize(_ *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -106,8 +110,16 @@ func Initialize(_ *cobra.Command, _ []string) error {
configData.KeyringBackend = config.KeyringBackend(initArgs.KeyringBackend)
configData.HsmMode = initArgs.HsmMode
configData.HsmHotKey = initArgs.HsmHotKey
configData.SolanaKeyFile = initArgs.SolanaKey
configData.ComplianceConfig = testutils.ComplianceConfigTest()

//Save config file
// Save solana test fee payer key file
keyFile := path.Join(rootArgs.zetaCoreHome, initArgs.SolanaKey)
err = createSolanaTestKeyFile(keyFile)
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

// Save config file
return config.Save(&configData, rootArgs.zetaCoreHome)
}
37 changes: 37 additions & 0 deletions cmd/zetaclientd/solana_test_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"encoding/json"
"os"
)

// solanaTestKey is a local test private key for Solana
// TODO: use separate keys for each zetaclient in Solana E2E tests
// https://github.com/zeta-chain/node/issues/2614
var solanaTestKey = []uint8{
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
199, 16, 63, 28, 125, 103, 131, 13, 6, 94, 68, 109, 13, 68, 132, 17,
71, 33, 216, 51, 49, 103, 146, 241, 245, 162, 90, 228, 71, 177, 32, 199,
31, 128, 124, 2, 23, 207, 48, 93, 141, 113, 91, 29, 196, 95, 24, 137,
170, 194, 90, 4, 124, 113, 12, 222, 166, 209, 119, 19, 78, 20, 99, 5,
}

// createSolanaTestKeyFile creates a solana test key json file
func createSolanaTestKeyFile(keyFile string) error {
// marshal the byte array to JSON
keyBytes, err := json.Marshal(solanaTestKey)
if err != nil {
return err
}

// create file (or overwrite if it already exists)
// #nosec G304 -- for E2E testing purposes only
file, err := os.Create(keyFile)
if err != nil {
return err
}
defer file.Close()

// write the key bytes to the file
_, err = file.Write(keyBytes)
return err
}
2 changes: 0 additions & 2 deletions cmd/zetaclientd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,3 @@ func CreateZetacoreClient(cfg config.Config, hotkeyPassword string, logger zerol

return client, nil
}

// TODO
6 changes: 5 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
logger.Print("❌ solana client is nil, maybe solana rpc is not set")
os.Exit(1)
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, e2etests.TestSolanaDepositName))
solanaTests := []string{
e2etests.TestSolanaDepositName,
e2etests.TestSolanaWithdrawName,
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...))
}

// while tests are executed, monitor blocks in parallel to check if system txs are on top and they have biggest priority
Expand Down
5 changes: 5 additions & 0 deletions contrib/localnet/orchestrator/start-zetae2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ address=$(yq -r '.additional_accounts.user_bitcoin.evm_address' config.yml)
echo "funding bitcoin tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545

# unlock solana tester accounts
address=$(yq -r '.additional_accounts.user_solana.evm_address' config.yml)
echo "funding solana tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545

# unlock ethers tester accounts
address=$(yq -r '.additional_accounts.user_ether.evm_address' config.yml)
echo "funding ether tester address ${address} with 10000 Ether"
Expand Down
3 changes: 3 additions & 0 deletions contrib/localnet/scripts/start-zetacored.sh
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ then
# bitcoin tester
address=$(yq -r '.additional_accounts.user_bitcoin.bech32_address' /root/config.yml)
zetacored add-genesis-account "$address" 100000000000000000000000000azeta
# solana tester
address=$(yq -r '.additional_accounts.user_solana.bech32_address' /root/config.yml)
zetacored add-genesis-account "$address" 100000000000000000000000000azeta
# ethers tester
address=$(yq -r '.additional_accounts.user_ether.bech32_address' /root/config.yml)
zetacored add-genesis-account "$address" 100000000000000000000000000azeta
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Broadcast message UpdateGatewayContract to update the gateway contract address

```
zetacored tx fungible update-gateway-contract [contract-address] [flags]
zetacored tx fungible update-gateway-contract [contract-address] [flags]
```

### Options
Expand Down
13 changes: 11 additions & 2 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ const (
/*
Solana tests
*/
TestSolanaDepositName = "solana_deposit"
TestSolanaDepositName = "solana_deposit"
TestSolanaWithdrawName = "solana_withdraw"

/*
Bitcoin tests
Expand Down Expand Up @@ -338,10 +339,18 @@ var AllE2ETests = []runner.E2ETest{
TestSolanaDepositName,
"deposit SOL into ZEVM",
[]runner.ArgDefinition{
{Description: "amount in SOL", DefaultValue: "0.1"},
{Description: "amount in lamport", DefaultValue: "13370000"},
},
TestSolanaDeposit,
),
runner.NewE2ETest(
TestSolanaWithdrawName,
"withdraw SOL from ZEVM",
[]runner.ArgDefinition{
{Description: "amount in lamport", DefaultValue: "1336000"},
},
TestSolanaWithdraw,
),
/*
Bitcoin tests
*/
Expand Down
17 changes: 13 additions & 4 deletions e2e/e2etests/test_solana_deposit.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
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"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
)

func TestSolanaDeposit(r *runner.E2ERunner, _ []string) {
func TestSolanaDeposit(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])))

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

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

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, privkey)
Expand Down
47 changes: 47 additions & 0 deletions e2e/e2etests/test_solana_withdraw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package e2etests

import (
"math/big"

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

"github.com/zeta-chain/zetacore/e2e/runner"
)

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)
require.NoError(r, err)
r.Logger.Info("from address %s balance of SOL before: %d", r.ZEVMAuth.From, balanceBefore)

// parse withdraw amount (in lamports), approve amount is 1 SOL
approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL)
// #nosec G115 e2e - always in range
withdrawAmount := big.NewInt(int64(parseInt(r, args[0])))
require.Equal(
r,
-1,
withdrawAmount.Cmp(approvedAmount),
"Withdrawal amount must be less than the approved amount (1e9).",
)

// load deployer private key
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)

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

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

// check if the balance is reduced correctly
amountReduced := new(big.Int).Sub(balanceBefore, balanceAfter)
require.True(r, amountReduced.Cmp(withdrawAmount) >= 0, "balance is not reduced correctly")
}
32 changes: 11 additions & 21 deletions e2e/runner/setup_solana.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,35 @@
package runner

import (
"time"

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/near/borsh-go"
"github.com/stretchr/testify/require"

"github.com/zeta-chain/zetacore/pkg/chains"
solanacontract "github.com/zeta-chain/zetacore/pkg/contract/solana"
solanacontracts "github.com/zeta-chain/zetacore/pkg/contracts/solana"
)

// SetupSolanaAccount imports the deployer's private key
func (r *E2ERunner) SetupSolanaAccount() {
r.Logger.Print("⚙️ setting up Solana account")
startTime := time.Now()
defer func() {
r.Logger.Print("✅ Solana account setup in %s", time.Since(startTime))
}()

r.SetSolanaAddress()
}

// SetSolanaAddress imports the deployer's private key
func (r *E2ERunner) SetSolanaAddress() {
privateKey := solana.MustPrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
privateKey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
require.NoError(r, err)
r.SolanaDeployerAddress = privateKey.PublicKey()

r.Logger.Info("SolanaDeployerAddress: %s", r.SolanaDeployerAddress)
}

// SetSolanaContracts set Solana contracts
func (r *E2ERunner) SetSolanaContracts(deployerPrivateKey string) {
r.Logger.Print("⚙️ setting up Solana contracts")
r.Logger.Print("⚙️ initializing gateway program on Solana")

// set Solana contracts
r.GatewayProgram = solana.MustPublicKeyFromBase58(solanacontract.SolanaGatewayProgramID)
r.GatewayProgram = solana.MustPublicKeyFromBase58(solanacontracts.SolanaGatewayProgramID)

// get deployer account balance
privkey := solana.MustPrivateKeyFromBase58(deployerPrivateKey)
privkey, err := solana.PrivateKeyFromBase58(deployerPrivateKey)
require.NoError(r, err)
bal, err := r.SolanaClient.GetBalance(r.Ctx, privkey.PublicKey(), rpc.CommitmentFinalized)
require.NoError(r, err)
r.Logger.Info("deployer address: %s, balance: %f SOL", privkey.PublicKey().String(), float64(bal.Value)/1e9)
Expand All @@ -57,8 +47,8 @@ func (r *E2ERunner) SetSolanaContracts(deployerPrivateKey string) {
inst.ProgID = r.GatewayProgram
inst.AccountValues = accountSlice

inst.DataBytes, err = borsh.Serialize(solanacontract.InitializeParams{
Discriminator: solanacontract.DiscriminatorInitialize(),
inst.DataBytes, err = borsh.Serialize(solanacontracts.InitializeParams{
Discriminator: solanacontracts.DiscriminatorInitialize(),
TssAddress: r.TSSAddress,
ChainID: uint64(chains.SolanaLocalnet.ChainId),
})
Expand All @@ -76,7 +66,7 @@ func (r *E2ERunner) SetSolanaContracts(deployerPrivateKey string) {
require.NoError(r, err)

// deserialize the PDA info
pda := solanacontract.PdaInfo{}
pda := solanacontracts.PdaInfo{}
err = borsh.Deserialize(&pda, pdaInfo.Bytes())
require.NoError(r, err)
tssAddress := ethcommon.BytesToAddress(pda.TssAddress[:])
Expand Down
28 changes: 27 additions & 1 deletion e2e/runner/solana.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package runner

import (
"math/big"
"time"

ethcommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -9,7 +10,9 @@ import (
"github.com/near/borsh-go"
"github.com/stretchr/testify/require"

solanacontract "github.com/zeta-chain/zetacore/pkg/contract/solana"
"github.com/zeta-chain/zetacore/e2e/utils"
solanacontract "github.com/zeta-chain/zetacore/pkg/contracts/solana"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
)

// ComputePdaAddress computes the PDA address for the gateway program
Expand Down Expand Up @@ -105,3 +108,26 @@ func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, *

return sig, out
}

// WithdrawSOLZRC20 withdraws an amount of ZRC20 SOL tokens
func (r *E2ERunner) WithdrawSOLZRC20(to solana.PublicKey, amount *big.Int, approveAmount *big.Int) {
// approve
tx, err := r.SOLZRC20.Approve(r.ZEVMAuth, r.SOLZRC20Addr, approveAmount)
require.NoError(r, err)
receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt)

// withdraw
tx, err = r.SOLZRC20.Withdraw(r.ZEVMAuth, []byte(to.String()), amount)
require.NoError(r, err)
r.Logger.EVMTransaction(*tx, "withdraw")

// wait for tx receipt
receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt)
r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status)

// 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)
}
2 changes: 1 addition & 1 deletion e2e/txserver/zeta_tx_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20(
100000,
))
if err != nil {
return SystemContractAddresses{}, fmt.Errorf("failed to deploy btc zrc20: %s", err.Error())
return SystemContractAddresses{}, fmt.Errorf("failed to deploy sol zrc20: %s", err.Error())
}

// deploy erc20 zrc20
Expand Down
Loading
Loading