-
Notifications
You must be signed in to change notification settings - Fork 111
/
Copy pathwithdraw_spl.go
147 lines (129 loc) · 4.7 KB
/
withdraw_spl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package signer
import (
"context"
"cosmossdk.io/errors"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/near/borsh-go"
"github.com/zeta-chain/node/pkg/chains"
contracts "github.com/zeta-chain/node/pkg/contracts/solana"
"github.com/zeta-chain/node/x/crosschain/types"
)
// createAndSignMsgWithdrawSPL creates and signs a withdraw spl message for gateway withdraw_spl instruction with TSS.
func (signer *Signer) createAndSignMsgWithdrawSPL(
ctx context.Context,
params *types.OutboundParams,
height uint64,
asset string,
decimals uint8,
cancelTx bool,
) (*contracts.MsgWithdrawSPL, error) {
chain := signer.Chain()
// #nosec G115 always positive
chainID := uint64(signer.Chain().ChainId)
nonce := params.TssNonce
amount := params.Amount.Uint64()
// zero out the amount if cancelTx is set. It's legal to withdraw 0 spl through the gateway.
if cancelTx {
amount = 0
}
// check receiver address
to, err := chains.DecodeSolanaWalletAddress(params.Receiver)
if err != nil {
return nil, errors.Wrapf(err, "cannot decode receiver address %s", params.Receiver)
}
// parse mint account
mintAccount, err := solana.PublicKeyFromBase58(asset)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse asset public key %s", asset)
}
// get recipient ata
recipientAta, _, err := solana.FindAssociatedTokenAddress(to, mintAccount)
if err != nil {
return nil, errors.Wrapf(err, "cannot find ATA for %s and mint account %s", to, mintAccount)
}
// prepare withdraw spl msg and compute hash
msg := contracts.NewMsgWithdrawSPL(chainID, nonce, amount, decimals, mintAccount, to, recipientAta)
msgHash := msg.Hash()
// sign the message with TSS to get an ECDSA signature.
// the produced signature is in the [R || S || V] format where V is 0 or 1.
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId)
if err != nil {
return nil, errors.Wrap(err, "Key-sign failed")
}
// attach the signature and return
return msg.SetSignature(signature), nil
}
// signWithdrawSPLTx wraps the withdraw spl 'msg' into a Solana transaction and signs it with the relayer key.
func (signer *Signer) signWithdrawSPLTx(
ctx context.Context,
msg contracts.MsgWithdrawSPL,
) (*solana.Transaction, error) {
// create withdraw spl instruction with program call data
dataBytes, err := borsh.Serialize(contracts.WithdrawSPLInstructionParams{
Discriminator: contracts.DiscriminatorWithdrawSPL,
Decimals: msg.Decimals(),
Amount: msg.Amount(),
Signature: msg.SigRS(),
RecoveryID: msg.SigV(),
MessageHash: msg.Hash(),
Nonce: msg.Nonce(),
})
if err != nil {
return nil, errors.Wrap(err, "cannot serialize withdraw instruction")
}
pdaAta, _, err := solana.FindAssociatedTokenAddress(signer.pda, msg.MintAccount())
if err != nil {
return nil, errors.Wrapf(err, "cannot find ATA for %s and mint account %s", signer.pda, msg.MintAccount())
}
recipientAta, _, err := solana.FindAssociatedTokenAddress(msg.To(), msg.MintAccount())
if err != nil {
return nil, errors.Wrapf(err, "cannot find ATA for %s and mint account %s", msg.To(), msg.MintAccount())
}
inst := solana.GenericInstruction{
ProgID: signer.gatewayID,
DataBytes: dataBytes,
AccountValues: []*solana.AccountMeta{
solana.Meta(signer.relayerKey.PublicKey()).WRITE().SIGNER(),
solana.Meta(signer.pda).WRITE(),
solana.Meta(pdaAta).WRITE(),
solana.Meta(msg.MintAccount()),
solana.Meta(msg.To()),
solana.Meta(recipientAta).WRITE(),
solana.Meta(signer.rentPayerPda).WRITE(),
solana.Meta(solana.TokenProgramID),
solana.Meta(solana.SPLAssociatedTokenAccountProgramID),
solana.Meta(solana.SystemProgramID),
},
}
// get a recent blockhash
recent, err := signer.client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
if err != nil {
return nil, errors.Wrap(err, "GetLatestBlockhash error")
}
// create a transaction that wraps the instruction
tx, err := solana.NewTransaction(
[]solana.Instruction{
// TODO: outbound now uses 5K lamports as the fixed fee, we could explore priority fee and compute budget
// https://github.com/zeta-chain/node/issues/2599
// programs.ComputeBudgetSetComputeUnitLimit(computeUnitLimit),
// programs.ComputeBudgetSetComputeUnitPrice(computeUnitPrice),
&inst},
recent.Value.Blockhash,
solana.TransactionPayer(signer.relayerKey.PublicKey()),
)
if err != nil {
return nil, errors.Wrap(err, "NewTransaction error")
}
// relayer signs the transaction
_, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey {
if key.Equals(signer.relayerKey.PublicKey()) {
return signer.relayerKey
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "signer unable to sign transaction")
}
return tx, nil
}