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 12 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
44 changes: 43 additions & 1 deletion cmd/zetaclientd/init.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package main

import (
"encoding/json"
"os"
"path"

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

"github.com/zeta-chain/zetacore/zetaclient/config"
"github.com/zeta-chain/zetacore/zetaclient/testutils"
)

// solanaTestKey is a local test private key for Solana
var solanaTestKey = []uint8{
lumtis marked this conversation as resolved.
Show resolved Hide resolved
lumtis 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,
}

var InitCmd = &cobra.Command{
Use: "init",
Short: "Initialize Configuration",
Expand Down Expand Up @@ -36,6 +48,7 @@ type initArguments struct {
KeyringBackend string
HsmMode bool
HsmHotKey string
SolanaKey string
}

func init() {
Expand Down Expand Up @@ -69,6 +82,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 +120,36 @@ 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 fee payer key
keyFile := path.Join(rootArgs.zetaCoreHome, initArgs.SolanaKey)
err = createSolanaKeyFile(keyFile)
if err != nil {
return err
}

// Save config file
return config.Save(&configData, rootArgs.zetaCoreHome)
}

// createSolanaKeyFile creates a solana key json file
func createSolanaKeyFile(keyFile string) error {
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
// marshal the byte array to JSON
keyBytes, err := json.Marshal(solanaTestKey)
if err != nil {
return err
}

// create file (or overwrite if it already exists)
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 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
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

# 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
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
14 changes: 11 additions & 3 deletions e2e/e2etests/test_solana_deposit.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
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)
depositAmount, ok := new(big.Int).SetString(args[0], 10)
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
require.True(r, ok, "Invalid deposit amount specified for TestSolanaDeposit.")

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

// 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
43 changes: 43 additions & 0 deletions e2e/e2etests/test_solana_withdraw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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 balance of from address
solZRC20 := r.SOLZRC20
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
balance, err := 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, balance)

// parse withdraw amount (in lamports), approve amount is 1 SOL
approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL)
withdrawAmount, ok := new(big.Int).SetString(args[0], 10)
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
require.True(r, ok, "Invalid withdrawal amount specified for TestSolanaWithdraw.")
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 := solana.MustPrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())

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

// print balance of from address after withdraw
balance, err = 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, balance)
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
}
16 changes: 2 additions & 14 deletions e2e/runner/setup_solana.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package runner

import (
"time"

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
Expand All @@ -13,18 +11,8 @@ import (
solanacontract "github.com/zeta-chain/zetacore/pkg/contract/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())
r.SolanaDeployerAddress = privateKey.PublicKey()

Expand All @@ -33,7 +21,7 @@ func (r *E2ERunner) SetSolanaAddress() {

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

// set Solana contracts
r.GatewayProgram = solana.MustPublicKeyFromBase58(solanacontract.SolanaGatewayProgramID)
Expand Down
26 changes: 26 additions & 0 deletions 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"

"github.com/zeta-chain/zetacore/e2e/utils"
solanacontract "github.com/zeta-chain/zetacore/pkg/contract/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) {
// approve
tx, err := r.SOLZRC20.Approve(r.ZEVMAuth, r.SOLZRC20Addr, big.NewInt(1e18))
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
19 changes: 19 additions & 0 deletions pkg/contract/solana/contract.go → pkg/contract/solana/gateway.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package solana
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

import (
"github.com/gagliardetto/solana-go"
)

const (
// SolanaGatewayProgramID is the program ID of the Solana gateway program
SolanaGatewayProgramID = "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d"
Expand Down Expand Up @@ -36,3 +40,18 @@
func DiscriminatorWithdrawSPL() [8]byte {
return [8]byte{156, 234, 11, 89, 235, 246, 32}
}

// ParseGatewayAddressAndPda parses the gateway id and program derived address from the given string
func ParseGatewayIDAndPda(address string) (gatewayID solana.PublicKey, pda solana.PublicKey, err error) {

Check warning on line 45 in pkg/contract/solana/gateway.go

View check run for this annotation

Codecov / codecov/patch

pkg/contract/solana/gateway.go#L45

Added line #L45 was not covered by tests
// decode gateway address
gatewayID, err = solana.PublicKeyFromBase58(address)
if err != nil {
return

Check warning on line 49 in pkg/contract/solana/gateway.go

View check run for this annotation

Codecov / codecov/patch

pkg/contract/solana/gateway.go#L47-L49

Added lines #L47 - L49 were not covered by tests
}

// compute gateway PDA
seed := []byte(PDASeed)
pda, _, err = solana.FindProgramAddress([][]byte{seed}, gatewayID)

Check warning on line 54 in pkg/contract/solana/gateway.go

View check run for this annotation

Codecov / codecov/patch

pkg/contract/solana/gateway.go#L53-L54

Added lines #L53 - L54 were not covered by tests

return

Check warning on line 56 in pkg/contract/solana/gateway.go

View check run for this annotation

Codecov / codecov/patch

pkg/contract/solana/gateway.go#L56

Added line #L56 was not covered by tests
}
Loading
Loading