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 6 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
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
10 changes: 9 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration))
)

if !testSolana {
testSolana = true
}

logger := runner.NewLogger(verbose, color.FgWhite, "setup")

testStartTime := time.Now()
Expand Down Expand Up @@ -314,7 +318,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
eg.Go(miscTestRoutine(conf, deployerRunner, verbose, e2etests.TestMyTestName))
}
if testSolana {
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
3 changes: 0 additions & 3 deletions contrib/localnet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,6 @@ services:
image: solana-local:latest
container_name: solana
hostname: solana
profiles:
- solana
- all
ports:
- "8899:8899"
networks:
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 @@ -62,6 +62,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

# 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)
require.True(r, ok, "Invalid deposit amount specified for TestSolanaDeposit.")

// load deployer private key
privkey := solana.MustPrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())

// 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
69 changes: 69 additions & 0 deletions e2e/e2etests/test_solana_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/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 TestSolanaWithdraw(r *runner.E2ERunner, args []string) {
require.Len(r, args, 1)

r.Logger.Print("TestSolanaWithdraw...sol zrc20 %s", r.SOLZRC20Addr.String())

solZRC20 := r.SOLZRC20
supply, err := solZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From)
if err != nil {
r.Logger.Error("Error getting total supply of sol zrc20: %v", err)
panic(err)
}
r.Logger.Print(" from %s supply of %s sol zrc20: %d", r.ZEVMAuth.From, r.EVMAddress(), supply)

// 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)
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
privkey := solana.MustPrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
r.Logger.Print("TestSolanaWithdraw...sol zrc20 %s", r.SOLZRC20Addr.String())

// 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(privkey.PublicKey().String()), withdrawAmount)
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.Print("Receipt txhash %s status %d", receipt.TxHash, receipt.Status)

supply, err = solZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From)
if err != nil {
r.Logger.Error("Error getting total supply of sol zrc20: %v", err)
panic(err)
}
r.Logger.Print(" from %s supply of %s sol zrc20 after: %d", r.ZEVMAuth.From, r.EVMAddress(), supply)

// 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)
}
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
37 changes: 37 additions & 0 deletions e2e/runner/solana.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package runner

import (
"math/big"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
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/e2e/utils"
solanacontract "github.com/zeta-chain/zetacore/pkg/contract/solana"
)

Expand Down Expand Up @@ -105,3 +108,37 @@ 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) {
// load deployer private key
privkey := solana.MustPrivateKeyFromBase58(r.Account.SolanaPrivateKey.String())
r.Logger.Print("TestSolanaWithdraw...sol zrc20 %s to %s", r.SOLZRC20Addr.String(), to.String())

solZRC20 := r.SOLZRC20
supply, err := solZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From)
if err != nil {
r.Logger.Error("Error getting total supply of sol zrc20: %v", err)
panic(err)
}
r.Logger.Print(" supply of %s sol zrc20: %d", r.EVMAddress(), supply)

// approve SOL token (approve big amount to cover withdraw fee)
approveAmount := big.NewInt(1e9 * 100) // 100 SOL
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 'amount' of SOL (lamports) from zEVM to Solana chain address
tx, err = r.SOLZRC20.Withdraw(r.ZEVMAuth, []byte(privkey.PublicKey().String()), amount)
require.NoError(r, err)

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

cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
r.Logger.CCTX(*cctx, "withdraw")
}
2 changes: 1 addition & 1 deletion e2e/txserver/zeta_tx_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20(
100000,
))
if err != nil {
return "", "", "", "", "", fmt.Errorf("failed to deploy btc zrc20: %s", err.Error())
return "", "", "", "", "", 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

import (
solanago "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 DiscriminatorWithdraw() [8]byte {
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 solanago.PublicKey, pda solanago.PublicKey, err error) {
// decode gateway address
gatewayID, err = solanago.PublicKeyFromBase58(address)
if err != nil {
return
}

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

return
}
73 changes: 73 additions & 0 deletions pkg/contract/solana/gateway_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package solana

import (
"encoding/binary"

"github.com/ethereum/go-ethereum/crypto"
"github.com/gagliardetto/solana-go"
)

// MsgWithdraw is the message for the Solana gateway withdraw instruction
type MsgWithdraw struct {
// ChainID is the chain ID of Solana chain
ChainID uint64

// Nonce is the nonce for the withdraw
Nonce uint64

// Amount is the lamports amount for the withdraw
Amount uint64

// To is the recipient address for the withdraw
To solana.PublicKey

// Signature is the signature of the message
Signature [65]byte
}

// NewMsgWithdraw returns a new MsgWithdraw
func NewMsgWithdraw(chainID, nonce, amount uint64, to solana.PublicKey) *MsgWithdraw {
return &MsgWithdraw{
ChainID: chainID,
Nonce: nonce,
Amount: amount,
To: to,
}
}

// Hash packs the withdraw message and computes the hash
func (msg *MsgWithdraw) Hash() [32]byte {
var message []byte
buff := make([]byte, 8)

binary.BigEndian.PutUint64(buff, msg.ChainID)
message = append(message, buff...)

binary.BigEndian.PutUint64(buff, msg.Nonce)
message = append(message, buff...)

binary.BigEndian.PutUint64(buff, msg.Amount)
message = append(message, buff...)

message = append(message, msg.To.Bytes()...)

return crypto.Keccak256Hash(message)
}

// WithSignature attaches the signature to the message
func (msg *MsgWithdraw) WithSignature(signature [65]byte) *MsgWithdraw {
msg.Signature = signature
return msg
}

// Sig64 returns the 64-byte [R+S] core part of the signature
func (msg *MsgWithdraw) SigRS() [64]byte {
var sig [64]byte
copy(sig[:], msg.Signature[:64])
return sig
}

// SigV returns the V part (recovery ID) of the signature
func (msg *MsgWithdraw) SigV() uint8 {
return msg.Signature[64]
}
Loading
Loading