Skip to content

Commit

Permalink
Withdrawals WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
swift1337 committed Feb 20, 2025
1 parent 5d31e75 commit ce8a8b5
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 6 deletions.
4 changes: 2 additions & 2 deletions zetaclient/chains/sui/client/client_live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const (

func TestClientLive(t *testing.T) {
if !common.LiveTestEnabled() {
// t.Skip("skipping live test")
// return
t.Skip("skipping live test")
return
}

t.Run("HealthCheck", func(t *testing.T) {
Expand Down
80 changes: 77 additions & 3 deletions zetaclient/chains/sui/signer/signer.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,87 @@
package signer

import "github.com/zeta-chain/node/zetaclient/chains/base"
import (
"context"
"crypto/sha256"

"github.com/block-vision/sui-go-sdk/models"
"github.com/pkg/errors"

"github.com/zeta-chain/node/pkg/contracts/sui"
cctypes "github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
)

// Signer Sui outbound transaction signer.
type Signer struct {
*base.Signer
client RPC
gateway *sui.Gateway
withdrawCap *withdrawCap
}

// RPC represents Sui rpc.
type RPC interface {
GetOwnedObjectID(ctx context.Context, ownerAddress, structType string) (string, error)

MoveCall(ctx context.Context, req models.MoveCallRequest) (models.TxnMetaData, error)
SuiExecuteTransactionBlock(
ctx context.Context,
req models.SuiExecuteTransactionBlockRequest,
) (models.SuiTransactionBlockResponse, error)
}

// New Signer constructor.
func New(baseSigner *base.Signer) *Signer {
return &Signer{Signer: baseSigner}
func New(baseSigner *base.Signer, client RPC, gateway *sui.Gateway) *Signer {
return &Signer{
Signer: baseSigner,
client: client,
gateway: gateway,
withdrawCap: &withdrawCap{},
}
}

// ProcessCCTX schedules outbound cross-chain transaction.
func (s *Signer) ProcessCCTX(ctx context.Context, cctx *cctypes.CrossChainTx, zetaHeight uint64) error {
// todo ... vote if confirmed, etc ...

nonce := cctx.GetCurrentOutboundParam().TssNonce

tx, err := s.buildWithdrawal(ctx, cctx)
if err != nil {
return errors.Wrap(err, "unable to build withdrawal tx")
}

Check warning on line 53 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L45-L53

Added lines #L45 - L53 were not covered by tests

sig, err := s.signTx(ctx, tx, zetaHeight, nonce)
if err != nil {
return errors.Wrap(err, "unable to sign tx")
}

Check warning on line 58 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L55-L58

Added lines #L55 - L58 were not covered by tests

if err := s.broadcast(ctx, tx, sig); err != nil {
return errors.Wrap(err, "unable to broadcast tx")
}

Check warning on line 62 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L60-L62

Added lines #L60 - L62 were not covered by tests

// todo ...

return nil

Check warning on line 66 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L66

Added line #L66 was not covered by tests
}

func (s *Signer) signTx(ctx context.Context, tx models.TxnMetaData, zetaHeight, nonce uint64) ([65]byte, error) {
digest, err := sui.Digest(tx)
if err != nil {
return [65]byte{}, errors.Wrap(err, "unable to get digest")
}

Check warning on line 73 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L69-L73

Added lines #L69 - L73 were not covered by tests

// Another hashing is required for ECDSA.
// https://docs.sui.io/concepts/cryptography/transaction-auth/signatures#signature-requirements
digestWrapped := sha256.Sum256(digest[:])

// Send TSS signature request.
return s.TSS().Sign(
ctx,
digestWrapped[:],
zetaHeight,
nonce,
s.Chain().ChainId,
)

Check warning on line 86 in zetaclient/chains/sui/signer/signer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/signer.go#L77-L86

Added lines #L77 - L86 were not covered by tests
}
85 changes: 85 additions & 0 deletions zetaclient/chains/sui/signer/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package signer

import (
"context"
"strconv"

"github.com/block-vision/sui-go-sdk/models"
"github.com/pkg/errors"

"github.com/zeta-chain/node/pkg/coin"
"github.com/zeta-chain/node/pkg/contracts/sui"
cctypes "github.com/zeta-chain/node/x/crosschain/types"
)

const funcWithdraw = "withdraw"

// buildWithdrawal builds unsigned withdrawal transaction using CCTX and Sui RPC
// https://github.com/zeta-chain/protocol-contracts-sui/blob/0245ad3a2eb4001381625070fd76c87c165589b2/sources/gateway.move#L117
func (s *Signer) buildWithdrawal(ctx context.Context, cctx *cctypes.CrossChainTx) (tx models.TxnMetaData, err error) {
params := cctx.GetCurrentOutboundParam()
coinType := ""

// Basic common-sense validation & coin-type determination
switch {
case params.ReceiverChainId != s.Chain().ChainId:
return tx, errors.Errorf("invalid receiver chain id %d", params.ReceiverChainId)
case cctx.ProtocolContractVersion != cctypes.ProtocolContractVersion_V2:
v := cctx.ProtocolContractVersion
return tx, errors.Errorf("invalid protocol version %q", v)
case cctx.InboundParams == nil:
return tx, errors.New("inbound params are nil")
case cctx.InboundParams.IsCrossChainCall:
return tx, errors.New("withdrawAndCall is not supported yet")
case params.CoinType == coin.CoinType_Gas:
coinType = string(sui.SUI)
case params.CoinType == coin.CoinType_ERC20:
coinType = cctx.InboundParams.Asset
default:
return tx, errors.Errorf("unsupported coin type %q", params.CoinType.String())

Check warning on line 39 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L19-L39

Added lines #L19 - L39 were not covered by tests
}

var (
nonce = strconv.FormatUint(params.TssNonce, 10)
recipient = params.Receiver
amount = params.Amount.String()
gasBudget = strconv.FormatUint(params.CallOptions.GasLimit, 10)
)

withdrawCapID, err := s.getWithdrawCapIDCached(ctx)
if err != nil {
return tx, errors.Wrap(err, "unable to get withdraw cap ID")
}

Check warning on line 52 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L42-L52

Added lines #L42 - L52 were not covered by tests

req := models.MoveCallRequest{
Signer: s.TSS().PubKey().AddressSui(),
PackageObjectId: s.gateway.PackageID(),
Module: s.gateway.Module(),
Function: funcWithdraw,
TypeArguments: []any{coinType},
Arguments: []any{s.gateway.ObjectID(), amount, nonce, recipient, withdrawCapID},
GasBudget: gasBudget,
}

return s.client.MoveCall(ctx, req)

Check warning on line 64 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L54-L64

Added lines #L54 - L64 were not covered by tests
}

// broadcast attaches signature to tx and broadcasts it to Sui network
func (s *Signer) broadcast(ctx context.Context, tx models.TxnMetaData, sig [65]byte) error {
sigBase64, err := sui.SerializeSignatureECDSA(sig, s.TSS().PubKey().Bytes(true))
if err != nil {
return errors.Wrap(err, "unable to serialize signature")
}

Check warning on line 72 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L68-L72

Added lines #L68 - L72 were not covered by tests

req := models.SuiExecuteTransactionBlockRequest{
TxBytes: tx.TxBytes,
Signature: []string{sigBase64},
}

_, err = s.client.SuiExecuteTransactionBlock(ctx, req)
if err != nil {
return errors.Wrap(err, "unable to execute tx block")
}

Check warning on line 82 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L74-L82

Added lines #L74 - L82 were not covered by tests

return nil

Check warning on line 84 in zetaclient/chains/sui/signer/tx.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/tx.go#L84

Added line #L84 was not covered by tests
}
72 changes: 72 additions & 0 deletions zetaclient/chains/sui/signer/withdrawcap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package signer

import (
"context"
"sync"
"time"

"github.com/pkg/errors"
)

const withdrawCapTTL = 5 * time.Minute

// withdrawCap represents WithdrawCap (capability) object
// that is required as a "permission" to withdraw funds.
// Should belong to TSS address on Sui.
type withdrawCap struct {
objectID string
mu sync.RWMutex
fetchedAt time.Time
}

func (wc *withdrawCap) valid() bool {
wc.mu.RLock()
defer wc.mu.RUnlock()

if wc.objectID == "" {
return false
}

Check warning on line 28 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L22-L28

Added lines #L22 - L28 were not covered by tests

return time.Now().Sub(wc.fetchedAt) < withdrawCapTTL

Check warning on line 30 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L30

Added line #L30 was not covered by tests
}

func (wc *withdrawCap) set(objectID string) {
wc.mu.Lock()
defer wc.mu.Unlock()

wc.objectID = objectID
wc.fetchedAt = time.Now()

Check warning on line 38 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L33-L38

Added lines #L33 - L38 were not covered by tests
}

// getWithdrawCapIDCached getWithdrawCapID with withdrawCapTTL cache.
func (s *Signer) getWithdrawCapIDCached(ctx context.Context) (string, error) {
if s.withdrawCap.valid() {
return s.withdrawCap.objectID, nil
}

Check warning on line 45 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L42-L45

Added lines #L42 - L45 were not covered by tests

s.Logger().Std.Info().Msg("WithdrawCap cache expired, fetching new objectID")

objectID, err := s.getWithdrawCapID(ctx)
if err != nil {
return "", errors.Wrap(err, "unable to get withdraw cap ID")
}

Check warning on line 52 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L47-L52

Added lines #L47 - L52 were not covered by tests

s.withdrawCap.set(objectID)

s.Logger().Std.Info().Str("sui.object_id", objectID).Msg("WithdrawCap objectID fetched")

return objectID, nil

Check warning on line 58 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L54-L58

Added lines #L54 - L58 were not covered by tests
}

// getWithdrawCapID returns the objectID of the WithdrawCap. Should belong to TSS address on Sui.
func (s *Signer) getWithdrawCapID(ctx context.Context) (string, error) {
owner := s.TSS().PubKey().AddressSui()
structType := s.gateway.WithdrawCapType()

objectID, err := s.client.GetOwnedObjectID(ctx, owner, structType)
if err != nil {
return "", errors.Wrap(err, "unable to get owned object ID")
}

Check warning on line 69 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L62-L69

Added lines #L62 - L69 were not covered by tests

return objectID, nil

Check warning on line 71 in zetaclient/chains/sui/signer/withdrawcap.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/sui/signer/withdrawcap.go#L71

Added line #L71 was not covered by tests
}
2 changes: 1 addition & 1 deletion zetaclient/orchestrator/v2_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (oc *V2) bootstrapSui(ctx context.Context, chain zctx.Chain) (*sui.Sui, err

observer := suiobserver.New(baseObserver, suiClient, gateway)

signer := suisigner.New(oc.newBaseSigner(chain))
signer := suisigner.New(oc.newBaseSigner(chain), suiClient, gateway)

return sui.New(oc.scheduler, observer, signer), nil
}
Expand Down

0 comments on commit ce8a8b5

Please sign in to comment.