Skip to content

Commit

Permalink
feat: add Whitelist message ability to whitelist SPL tokens on Solana (
Browse files Browse the repository at this point in the history
…#2984)

* whitelist spl mint wip

* whitelist test wip

* test fixes

* fmt

* small refactoring

* add protocol contracts solana pkg

* bump go idl pkg

* revert back to development gateway keypair

* fix e2e test

* add tss signature to whitelist spl mint

* CI fixes

* changelog

* cleanup parse code instruction for whitelist

* cleanup

* cleanup comments

* add whitelist candidate to msg hash

* PR comments

* fix after merge

* move back to tests solana and add todo

* PR comments
  • Loading branch information
skosito authored Nov 4, 2024
1 parent a35a571 commit b317941
Show file tree
Hide file tree
Showing 32 changed files with 1,573 additions and 162 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

* [2984](https://github.com/zeta-chain/node/pull/2984) - add Whitelist message ability to whitelist SPL tokens on Solana

## v21.0.0

### Features
Expand Down
3 changes: 3 additions & 0 deletions cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
e2etests.TestSolanaDepositAndCallRefundName,
e2etests.TestSolanaDepositRestrictedName,
e2etests.TestSolanaWithdrawRestrictedName,
// TODO move under admin tests
// https://github.com/zeta-chain/node/issues/3085
e2etests.TestSolanaWhitelistSPLName,
}
eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...))
}
Expand Down
1 change: 1 addition & 0 deletions cmd/zetae2e/local/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func solanaTestRoutine(
deployerRunner,
conf.AdditionalAccounts.UserSolana,
runner.NewLogger(verbose, color.FgCyan, "solana"),
runner.WithZetaTxServer(deployerRunner.ZetaTxServer),
)
if err != nil {
return err
Expand Down
Binary file modified contrib/localnet/solana/gateway.so
Binary file not shown.
7 changes: 7 additions & 0 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ const (
TestPauseERC20CustodyName = "pause_erc20_custody"
TestMigrateERC20CustodyFundsName = "migrate_erc20_custody_funds"
TestMigrateTSSName = "migrate_TSS"
TestSolanaWhitelistSPLName = "solana_whitelist_spl"

/*
V2 smart contract tests
Expand Down Expand Up @@ -453,6 +454,12 @@ var AllE2ETests = []runner.E2ETest{
},
TestSolanaWithdrawRestricted,
),
runner.NewE2ETest(
TestSolanaWhitelistSPLName,
"whitelist SPL",
[]runner.ArgDefinition{},
TestSolanaWhitelistSPL,
),
/*
TON tests
*/
Expand Down
68 changes: 68 additions & 0 deletions e2e/e2etests/test_solana_whitelist_spl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package e2etests

import (
"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"

"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/pkg/chains"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
)

func TestSolanaWhitelistSPL(r *runner.E2ERunner, _ []string) {
// Deploy a new SPL
r.Logger.Info("Deploying new SPL")

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

spl := r.DeploySPL(&privkey)

// check that whitelist entry doesn't exist for this spl
seed := [][]byte{[]byte("whitelist"), spl.PublicKey().Bytes()}
whitelistEntryPDA, _, err := solana.FindProgramAddress(seed, r.GatewayProgram)
require.NoError(r, err)

whitelistEntryInfo, err := r.SolanaClient.GetAccountInfo(r.Ctx, whitelistEntryPDA)
require.Error(r, err)
require.Nil(r, whitelistEntryInfo)

// whitelist sol zrc20
r.Logger.Info("whitelisting spl on new network")
res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, crosschaintypes.NewMsgWhitelistERC20(
r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName),
spl.PublicKey().String(),
chains.SolanaLocalnet.ChainId,
"TESTSPL",
"TESTSPL",
6,
100000,
))
require.NoError(r, err)

event, ok := txserver.EventOfType[*crosschaintypes.EventERC20Whitelist](res.Events)
require.True(r, ok, "no EventERC20Whitelist in %s", res.TxHash)
erc20zrc20Addr := event.Zrc20Address
whitelistCCTXIndex := event.WhitelistCctxIndex

err = r.ZetaTxServer.InitializeLiquidityCaps(erc20zrc20Addr)
require.NoError(r, err)

// ensure CCTX created
resCCTX, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: whitelistCCTXIndex})
require.NoError(r, err)

cctx := resCCTX.CrossChainTx
r.Logger.CCTX(*cctx, "whitelist_cctx")

// wait for the whitelist cctx to be mined
r.WaitForMinedCCTXFromIndex(whitelistCCTXIndex)

// check that whitelist entry exists for this spl
whitelistEntryInfo, err = r.SolanaClient.GetAccountInfo(r.Ctx, whitelistEntryPDA)
require.NoError(r, err)
require.NotNil(r, whitelistEntryInfo)
}
5 changes: 2 additions & 3 deletions e2e/runner/setup_solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,19 @@ func (r *E2ERunner) SetupSolana(deployerPrivateKey string) {
accountSlice = append(accountSlice, solana.Meta(privkey.PublicKey()).WRITE().SIGNER())
accountSlice = append(accountSlice, solana.Meta(pdaComputed).WRITE())
accountSlice = append(accountSlice, solana.Meta(solana.SystemProgramID))
accountSlice = append(accountSlice, solana.Meta(r.GatewayProgram))
inst.ProgID = r.GatewayProgram
inst.AccountValues = accountSlice

inst.DataBytes, err = borsh.Serialize(solanacontracts.InitializeParams{
Discriminator: solanacontracts.DiscriminatorInitialize(),
Discriminator: solanacontracts.DiscriminatorInitialize,
TssAddress: r.TSSAddress,
// #nosec G115 chain id always positive
ChainID: uint64(chains.SolanaLocalnet.ChainId),
})
require.NoError(r, err)

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{&inst}, privkey)
signedTx := r.CreateSignedTransaction([]solana.Instruction{&inst}, privkey, []solana.PrivateKey{})

// broadcast the transaction and wait for finalization
_, out := r.BroadcastTxSync(signedTx)
Expand Down
46 changes: 44 additions & 2 deletions e2e/runner/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/programs/token"
"github.com/gagliardetto/solana-go/rpc"
"github.com/near/borsh-go"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -49,7 +51,7 @@ func (r *E2ERunner) CreateDepositInstruction(

var err error
inst.DataBytes, err = borsh.Serialize(solanacontract.DepositInstructionParams{
Discriminator: solanacontract.DiscriminatorDeposit(),
Discriminator: solanacontract.DiscriminatorDeposit,
Amount: amount,
Memo: append(receiver.Bytes(), data...),
})
Expand All @@ -62,6 +64,7 @@ func (r *E2ERunner) CreateDepositInstruction(
func (r *E2ERunner) CreateSignedTransaction(
instructions []solana.Instruction,
privateKey solana.PrivateKey,
additionalPrivateKeys []solana.PrivateKey,
) *solana.Transaction {
// get a recent blockhash
recent, err := r.SolanaClient.GetLatestBlockhash(r.Ctx, rpc.CommitmentFinalized)
Expand All @@ -81,6 +84,11 @@ func (r *E2ERunner) CreateSignedTransaction(
if privateKey.PublicKey().Equals(key) {
return &privateKey
}
for _, apk := range additionalPrivateKeys {
if apk.PublicKey().Equals(key) {
return &apk
}
}
return nil
},
)
Expand All @@ -89,6 +97,40 @@ func (r *E2ERunner) CreateSignedTransaction(
return tx
}

func (r *E2ERunner) DeploySPL(privateKey *solana.PrivateKey) *solana.Wallet {
lamport, err := r.SolanaClient.GetMinimumBalanceForRentExemption(r.Ctx, token.MINT_SIZE, rpc.CommitmentFinalized)
require.NoError(r, err)

// to deploy new spl token, create account instruction and initialize mint instruction have to be in the same transaction
tokenAccount := solana.NewWallet()
createAccountInstruction := system.NewCreateAccountInstruction(
lamport,
token.MINT_SIZE,
solana.TokenProgramID,
privateKey.PublicKey(),
tokenAccount.PublicKey(),
).Build()

initializeMintInstruction := token.NewInitializeMint2Instruction(
6,
privateKey.PublicKey(),
privateKey.PublicKey(),
tokenAccount.PublicKey(),
).Build()

signedTx := r.CreateSignedTransaction(
[]solana.Instruction{createAccountInstruction, initializeMintInstruction},
*privateKey,
[]solana.PrivateKey{tokenAccount.PrivateKey},
)

// broadcast the transaction and wait for finalization
_, out := r.BroadcastTxSync(signedTx)
r.Logger.Info("create spl logs: %v", out.Meta.LogMessages)

return tokenAccount
}

// BroadcastTxSync broadcasts a transaction and waits for it to be finalized
func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, *rpc.GetTransactionResult) {
// broadcast the transaction
Expand Down Expand Up @@ -134,7 +176,7 @@ func (r *E2ERunner) SOLDepositAndCall(
instruction := r.CreateDepositInstruction(signerPrivKey.PublicKey(), receiver, data, amount.Uint64())

// create and sign the transaction
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, *signerPrivKey)
signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, *signerPrivKey, []solana.PrivateKey{})

// broadcast the transaction and wait for finalization
sig, out := r.BroadcastTxSync(signedTx)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ require (
github.com/bnb-chain/tss-lib v1.5.0
github.com/showa-93/go-mask v0.6.2
github.com/tonkeeper/tongo v1.9.3
github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2186,6 +2186,8 @@ github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlya
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw=
github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY=
github.com/gagliardetto/solana-go v1.10.0 h1:lDuHGC+XLxw9j8fCHBZM9tv4trI0PVhev1m9NAMaIdM=
github.com/gagliardetto/solana-go v1.10.0/go.mod h1:afBEcIRrDLJst3lvAahTr63m6W2Ns6dajZxe2irF7Jg=
github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw=
Expand Down Expand Up @@ -4212,6 +4214,8 @@ github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138 h1:vck/Fc
github.com/zeta-chain/keystone/keys v0.0.0-20240826165841-3874f358c138/go.mod h1:U494OsZTWsU75hqoriZgMdSsgSGP1mUL1jX+wN/Aez8=
github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c h1:ZoFxMMZtivRLquXVq1sEVlT45UnTPMO1MSXtc88nDv4=
github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20241021075719-d40d2e28467c/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w=
github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b h1:w4YVBbWxk9TI+7HM8hTvK66IgOo5XvEFsmH7n6WgW50=
github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20241025181051-d8d49e4fc85b/go.mod h1:DcDY828o773soiU/h0XpC+naxitrIMFVZqEvq/EJxMA=
github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901 h1:9whtN5fjYHfk4yXIuAsYP2EHxImwDWDVUOnZJ2pfL3w=
github.com/zeta-chain/tss-lib v0.0.0-20240916163010-2e6b438bd901/go.mod h1:d2iTC62s9JwKiCMPhcDDXbIZmuzAyJ4lwso0H5QyRbk=
github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
Expand Down
4 changes: 4 additions & 0 deletions pkg/chains/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func (chain Chain) IsEVMChain() bool {
return chain.Vm == Vm_evm
}

func (chain Chain) IsSolanaChain() bool {
return chain.Consensus == Consensus_solana_consensus
}

func (chain Chain) IsBitcoinChain() bool {
return chain.Consensus == Consensus_bitcoin
}
Expand Down
39 changes: 15 additions & 24 deletions pkg/contracts/solana/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package solana
import (
"github.com/gagliardetto/solana-go"
"github.com/pkg/errors"
idlgateway "github.com/zeta-chain/protocol-contracts-solana/go-idl/generated"
)

const (
Expand All @@ -18,30 +19,20 @@ const (
AccountsNumDeposit = 3
)

// DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction
func DiscriminatorInitialize() [8]byte {
return [8]byte{175, 175, 109, 31, 13, 152, 155, 237}
}

// DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction
func DiscriminatorDeposit() [8]byte {
return [8]byte{242, 35, 198, 137, 82, 225, 242, 182}
}

// DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction
func DiscriminatorDepositSPL() [8]byte {
return [8]byte{86, 172, 212, 121, 63, 233, 96, 144}
}

// DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction
func DiscriminatorWithdraw() [8]byte {
return [8]byte{183, 18, 70, 156, 148, 109, 161, 34}
}

// DiscriminatorWithdrawSPL returns the discriminator for Solana gateway 'withdraw_spl_token' instruction
func DiscriminatorWithdrawSPL() [8]byte {
return [8]byte{156, 234, 11, 89, 235, 246, 32}
}
var (
// DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction
DiscriminatorInitialize = idlgateway.IDLGateway.GetDiscriminator("initialize")
// DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction
DiscriminatorDeposit = idlgateway.IDLGateway.GetDiscriminator("deposit")
// DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction
DiscriminatorDepositSPL = idlgateway.IDLGateway.GetDiscriminator("deposit_spl_token")
// DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction
DiscriminatorWithdraw = idlgateway.IDLGateway.GetDiscriminator("withdraw")
// DiscriminatorWithdrawSPL returns the discriminator for Solana gateway 'withdraw_spl_token' instruction
DiscriminatorWithdrawSPL = idlgateway.IDLGateway.GetDiscriminator("withdraw_spl_token")
// DiscriminatorWhitelist returns the discriminator for Solana gateway 'whitelist_spl_mint' instruction
DiscriminatorWhitelistSplMint = idlgateway.IDLGateway.GetDiscriminator("whitelist_spl_mint")
)

// ParseGatewayAddressAndPda parses the gateway id and program derived address from the given string
func ParseGatewayIDAndPda(address string) (solana.PublicKey, solana.PublicKey, error) {
Expand Down
Loading

0 comments on commit b317941

Please sign in to comment.