diff --git a/bridges/stellar-solana/api/bridge/bridge.go b/bridges/stellar-solana/api/bridge/bridge.go index df93882..dac704f 100644 --- a/bridges/stellar-solana/api/bridge/bridge.go +++ b/bridges/stellar-solana/api/bridge/bridge.go @@ -104,21 +104,13 @@ func (bridge *Bridge) Close() error { return nil } -func (bridge *Bridge) mint(ctx context.Context, receiver solana.Address, depositedAmount *big.Int, txID string) (err error) { +func (bridge *Bridge) mint(ctx context.Context, memoAddress solana.Address, depositedAmount *big.Int, txID string) (err error) { if !bridge.synced { return errors.New("bridge is not synced, retry later") } - valid, err := bridge.solanaWallet.IsValidReceiver(ctx, receiver) - if err != nil { - return errors.Wrap(err, "Failed to check if receiver is proper") - } - - if !valid { - return faults.ErrInvalidReceiver - } - - log.Info().Str("receiver", receiver.String()).Str("txID", txID).Msg("Minting") + // Check if this tx is a known mint TX + log.Info().Str("receiver", memoAddress.String()).Str("txID", txID).Msg("Minting") // check if we already know this ID known, err := bridge.solanaWallet.IsMintTxID(ctx, txID) if err != nil { @@ -130,6 +122,33 @@ func (bridge *Bridge) mint(ctx context.Context, receiver solana.Address, deposit return } + // Check if we've already refunded this TX + known, err = bridge.wallet.TransactionStorage.TransactionWithMemoExists(ctx, txID) + if err != nil { + return + } + if known { + log.Info().Str("txID", txID).Msg("Skipping minting transaction we've already refunded") + // we already refunded this, so ignore the transaction + return + } + + // Convert receiver address to derived ATA + receiver, err := bridge.solanaWallet.ATAFromMasterAddress(memoAddress) + if err != nil { + return errors.Wrap(err, "could not convert memo master address to derived ATA") + } + + log.Debug().Str("ATA", receiver.String()).Msg("Checking if computed receiver ATA is valid") + valid, err := bridge.solanaWallet.IsValidReceiver(ctx, receiver) + if err != nil { + return errors.Wrap(err, "Failed to check if receiver is proper") + } + + if !valid { + return faults.ErrInvalidReceiver + } + depositFeeBigInt := big.NewInt(stellar.IntToStroops(bridge.config.DepositFee)) if depositedAmount.Cmp(depositFeeBigInt) <= 0 { diff --git a/bridges/stellar-solana/api/bridge/signer_server.go b/bridges/stellar-solana/api/bridge/signer_server.go index 7219c9e..42c03d2 100644 --- a/bridges/stellar-solana/api/bridge/signer_server.go +++ b/bridges/stellar-solana/api/bridge/signer_server.go @@ -87,6 +87,29 @@ func (s *SignerService) SignMint(ctx context.Context, request SolanaRequest, res return err } + // Check in transaction storage if the deposit transaction exists + tx, err := s.stellarWallet.TransactionStorage.GetTransactionWithID(ctx, request.TxID) + if err != nil { + log.Info().Str("txid", request.TxID).Msg("transaction not found") + return err + } + + known, err := s.solWallet.IsMintTxID(ctx, tx.ID) + if err != nil { + return errors.Wrap(err, "Could not verify if we already know this mint") + } + if known { + return errors.New("Refusing to sign mint request for transaction we already minted") + } + + known, err = s.stellarWallet.TransactionStorage.TransactionWithMemoExists(ctx, request.TxID) + if err != nil { + return errors.Wrap(err, "Could not verify if we already refunded this tx") + } + if known { + return errors.New("Refusing to sign mint request for transaction we already refunded") + } + amount, memo, receiver, err := solana.ExtractMintvalues(*solTx) if memo != request.TxID { log.Warn().Str("requested txid", request.TxID).Str("embedded txid", memo).Msg("could not unmarshal transaction") @@ -102,13 +125,6 @@ func (s *SignerService) SignMint(ctx context.Context, request SolanaRequest, res return errors.New("Tx receiver does not match requested receiver") } - // Check in transaction storage if the deposit transaction exists - tx, err := s.stellarWallet.TransactionStorage.GetTransactionWithID(ctx, request.TxID) - if err != nil { - log.Info().Str("txid", request.TxID).Msg("transaction not found") - return err - } - // Validate amount depositedAmount, _, err := s.stellarWallet.GetDepositAmountAndSender(request.TxID, s.bridgeMasterAddress) if err != nil { @@ -130,22 +146,21 @@ func (s *SignerService) SignMint(ctx context.Context, request SolanaRequest, res if tx.MemoType != "hash" { return errors.New("memo is not of type memo hash") } + + // Extract master address from stellar tx addr, err := solana.AddressFromB64(tx.Memo) if err != nil { return err } - if addr != request.Receiver { - return fmt.Errorf("deposit addresses do not match") - } - - known, err := s.solWallet.IsMintTxID(ctx, tx.ID) + // convert to ATA address, which is the one passed in the solana mint tx and the signing request + addr, err = s.solWallet.ATAFromMasterAddress(addr) if err != nil { - return errors.Wrap(err, "Could not verify if we already know this mint") + return err } - if known { - return errors.New("Refusing to sign mint request for transaction we already minted") + if addr != request.Receiver { + return fmt.Errorf("deposit addresses do not match") } signature, idx, err := s.solWallet.CreateTokenSignature(*solTx) diff --git a/bridges/stellar-solana/solana/solana.go b/bridges/stellar-solana/solana/solana.go index c037772..04d412e 100644 --- a/bridges/stellar-solana/solana/solana.go +++ b/bridges/stellar-solana/solana/solana.go @@ -344,6 +344,22 @@ func (sol *Solana) listTransactionSigs(ctx context.Context) ([]Signature, error) return signatures, nil } +// ATAFromMasterAddress derives the expected ATA for the current mint assuming the given +// adddress is the master account. +func (sol *Solana) ATAFromMasterAddress(master Address) (Address, error) { + // Rather than calling solana.FindAssociatedTokenAddress(master, sol.tokenAddress), we call the internal function that function call directly. + // The reason for this is that we use Token2022 programs, and the library uses the regular token program, which leads to different ATA derivations. + addr, _, err := solana.FindProgramAddress([][]byte{ + master[:], + solana.Token2022ProgramID[:], + sol.tokenAddress[:], + }, + solana.SPLAssociatedTokenAccountProgramID, + ) + + return addr, err +} + // AddressFromHex decodes a hex encoded Solana address func AddressFromHex(encoded string) (Address, error) { b, err := hex.DecodeString(encoded)