Skip to content

Commit

Permalink
fix: fix solana inbounds (#3255)
Browse files Browse the repository at this point in the history
  • Loading branch information
skosito committed Dec 9, 2024
1 parent 1329066 commit 87bf74f
Show file tree
Hide file tree
Showing 20 changed files with 845 additions and 158 deletions.
2 changes: 1 addition & 1 deletion e2e/e2etests/test_solana_deposit_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ func TestSolanaDepositAndCall(r *runner.E2ERunner, args []string) {
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// check if example contract has been called, bar value should be set to amount
utils.MustHaveCalledExampleContract(r, contract, depositAmount)
utils.MustHaveCalledExampleContractWithMsg(r, contract, depositAmount, data)
}
2 changes: 1 addition & 1 deletion e2e/e2etests/test_spl_deposit_and_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestSPLDepositAndCall(r *runner.E2ERunner, args []string) {
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)

// check if example contract has been called, bar value should be set to amount
utils.MustHaveCalledExampleContract(r, contract, big.NewInt(int64(amount)))
utils.MustHaveCalledExampleContractWithMsg(r, contract, big.NewInt(int64(amount)), data)

// verify balances are updated
pdaBalanceAfter, err := r.SolanaClient.GetTokenAccountBalance(r.Ctx, pdaAta, rpc.CommitmentFinalized)
Expand Down
48 changes: 36 additions & 12 deletions e2e/runner/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,24 @@ func (r *E2ERunner) CreateDepositInstruction(
data []byte,
amount uint64,
) solana.Instruction {
depositData, err := borsh.Serialize(solanacontract.DepositInstructionParams{
Discriminator: solanacontract.DiscriminatorDeposit,
Amount: amount,
Memo: append(receiver.Bytes(), data...),
})
require.NoError(r, err)
var err error
var depositData []byte
if data == nil {
depositData, err = borsh.Serialize(solanacontract.DepositInstructionParams{
Discriminator: solanacontract.DiscriminatorDeposit,
Amount: amount,
Receiver: receiver,
})
require.NoError(r, err)
} else {
depositData, err = borsh.Serialize(solanacontract.DepositAndCallInstructionParams{
Discriminator: solanacontract.DiscriminatorDepositAndCall,
Amount: amount,
Receiver: receiver,
Memo: data,
})
require.NoError(r, err)
}

return &solana.GenericInstruction{
ProgID: r.GatewayProgram,
Expand Down Expand Up @@ -87,12 +99,24 @@ func (r *E2ERunner) CreateDepositSPLInstruction(
receiver ethcommon.Address,
data []byte,
) solana.Instruction {
depositSPLData, err := borsh.Serialize(solanacontract.DepositInstructionParams{
Discriminator: solanacontract.DiscriminatorDepositSPL,
Amount: amount,
Memo: append(receiver.Bytes(), data...),
})
require.NoError(r, err)
var err error
var depositSPLData []byte
if data == nil {
depositSPLData, err = borsh.Serialize(solanacontract.DepositSPLInstructionParams{
Discriminator: solanacontract.DiscriminatorDepositSPL,
Amount: amount,
Receiver: receiver,
})
require.NoError(r, err)
} else {
depositSPLData, err = borsh.Serialize(solanacontract.DepositSPLAndCallInstructionParams{
Discriminator: solanacontract.DiscriminatorDepositSPLAndCall,
Amount: amount,
Receiver: receiver,
Memo: data,
})
require.NoError(r, err)
}

return &solana.GenericInstruction{
ProgID: r.GatewayProgram,
Expand Down
23 changes: 23 additions & 0 deletions e2e/utils/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,26 @@ func MustHaveCalledExampleContract(
amount.String(),
)
}

// MustHaveCalledExampleContractWithMsg checks if the contract has been called correctly with correct amount and msg
func MustHaveCalledExampleContractWithMsg(
t require.TestingT,
contract *testcontract.Example,
amount *big.Int,
msg []byte,
) {
bar, err := contract.Bar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(
t,
0,
bar.Cmp(amount),
"cross-chain call failed bar value %s should be equal to amount %s",
bar.String(),
amount.String(),
)

lastMsg, err := contract.LastMessage(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, string(msg), string(lastMsg))
}
6 changes: 6 additions & 0 deletions pkg/contracts/solana/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ var (
// DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction
DiscriminatorDeposit = idlgateway.IDLGateway.GetDiscriminator("deposit")

// DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit_and_call' instruction
DiscriminatorDepositAndCall = idlgateway.IDLGateway.GetDiscriminator("deposit_and_call")

// DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction
DiscriminatorDepositSPL = idlgateway.IDLGateway.GetDiscriminator("deposit_spl_token")

// DiscriminatorDepositSPLAndCall returns the discriminator for Solana gateway 'deposit_spl_token_and_call' instruction
DiscriminatorDepositSPLAndCall = idlgateway.IDLGateway.GetDiscriminator("deposit_spl_token_and_call")

// DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction
DiscriminatorWithdraw = idlgateway.IDLGateway.GetDiscriminator("withdraw")

Expand Down
132 changes: 116 additions & 16 deletions pkg/contracts/solana/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,33 @@ type Deposit struct {
Asset string
}

// ParseInboundAsDeposit tries to parse an instruction as a 'deposit'.
// It returns nil if the instruction can't be parsed as a 'deposit'.
// ParseInboundAsDeposit tries to parse an instruction as a 'deposit' or 'deposit_and_call'.
// It returns nil if the instruction can't be parsed.
func ParseInboundAsDeposit(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// first try to parse as deposit, then as deposit_and_call
deposit, err := tryParseAsDeposit(tx, instructionIndex, slot)
if err != nil || deposit != nil {
return deposit, err
}

return tryParseAsDepositAndCall(tx, instructionIndex, slot)
}

// tryParseAsDeposit tries to parse instruction as deposit
func tryParseAsDeposit(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// get instruction by index
instruction := tx.Message.Instructions[instructionIndex]

// try deserializing instruction as a 'deposit'
var inst DepositInstructionParams
// try deserializing instruction as a deposit
inst := DepositInstructionParams{}
err := borsh.Deserialize(&inst, instruction.Data)
if err != nil {
return nil, nil
Expand All @@ -51,30 +66,80 @@ func ParseInboundAsDeposit(
return &Deposit{
Sender: sender,
Amount: inst.Amount,
Memo: inst.Memo,
Memo: inst.Receiver[:],
Slot: slot,
Asset: "", // no asset for gas token SOL
}, nil
}

// tryParseAsDepositAndCall tries to parse instruction as deposit_and_call
func tryParseAsDepositAndCall(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// get instruction by index
instruction := tx.Message.Instructions[instructionIndex]

// try deserializing instruction as a deposit_and_call
instDepositAndCall := DepositAndCallInstructionParams{}
err := borsh.Deserialize(&instDepositAndCall, instruction.Data)
if err != nil {
return nil, nil
}

// check if the instruction is a deposit_and_call or not, if not, skip parsing
if instDepositAndCall.Discriminator != DiscriminatorDepositAndCall {
return nil, nil
}

Check warning on line 94 in pkg/contracts/solana/inbound.go

View check run for this annotation

Codecov / codecov/patch

pkg/contracts/solana/inbound.go#L93-L94

Added lines #L93 - L94 were not covered by tests

// get the sender address (skip if unable to parse signer address)
sender, err := getSignerDeposit(tx, &instruction)
if err != nil {
return nil, err
}
return &Deposit{
Sender: sender,
Amount: instDepositAndCall.Amount,
Memo: append(instDepositAndCall.Receiver[:], instDepositAndCall.Memo...),
Slot: slot,
Asset: "", // no asset for gas token SOL
}, nil
}

// ParseInboundAsDepositSPL tries to parse an instruction as a 'deposit_spl_token'.
// It returns nil if the instruction can't be parsed as a 'deposit_spl_token'.
// ParseInboundAsDepositSPL tries to parse an instruction as a deposit_spl or deposit_spl_and_call.
// It returns nil if the instruction can't be parsed as a deposit_spl.
func ParseInboundAsDepositSPL(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// first try to parse as deposit_spl, then as deposit_spl_and_call
deposit, err := tryParseAsDepositSPL(tx, instructionIndex, slot)
if err != nil || deposit != nil {
return deposit, err
}

return tryParseAsDepositSPLAndCall(tx, instructionIndex, slot)
}

// tryParseAsDepositSPL tries to parse instruction as deposit_spl
func tryParseAsDepositSPL(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// get instruction by index
instruction := tx.Message.Instructions[instructionIndex]

// try deserializing instruction as a 'deposit_spl_token'
// try deserializing instruction as a deposit_spl
var inst DepositSPLInstructionParams
err := borsh.Deserialize(&inst, instruction.Data)
if err != nil {
return nil, nil
}

// check if the instruction is a deposit spl or not, if not, skip parsing
// check if the instruction is a deposit_spl or not, if not, skip parsing
if inst.Discriminator != DiscriminatorDepositSPL {
return nil, nil
}
Expand All @@ -88,7 +153,42 @@ func ParseInboundAsDepositSPL(
return &Deposit{
Sender: sender,
Amount: inst.Amount,
Memo: inst.Memo,
Memo: inst.Receiver[:],
Slot: slot,
Asset: spl,
}, nil
}

// tryParseAsDepositSPLAndCall tries to parse instruction as deposit_spl_and_call
func tryParseAsDepositSPLAndCall(
tx *solana.Transaction,
instructionIndex int,
slot uint64,
) (*Deposit, error) {
// get instruction by index
instruction := tx.Message.Instructions[instructionIndex]

// try deserializing instruction as a deposit_spl_and_call
instDepositAndCall := DepositSPLAndCallInstructionParams{}
err := borsh.Deserialize(&instDepositAndCall, instruction.Data)
if err != nil {
return nil, nil
}

// check if the instruction is a deposit_spl_and_call or not, if not, skip parsing
if instDepositAndCall.Discriminator != DiscriminatorDepositSPLAndCall {
return nil, nil
}

Check warning on line 181 in pkg/contracts/solana/inbound.go

View check run for this annotation

Codecov / codecov/patch

pkg/contracts/solana/inbound.go#L180-L181

Added lines #L180 - L181 were not covered by tests

// get the sender and spl addresses
sender, spl, err := getSignerAndSPLFromDepositSPLAccounts(tx, &instruction)
if err != nil {
return nil, err
}
return &Deposit{
Sender: sender,
Amount: instDepositAndCall.Amount,
Memo: append(instDepositAndCall.Receiver[:], instDepositAndCall.Memo...),
Slot: slot,
Asset: spl,
}, nil
Expand All @@ -101,9 +201,9 @@ func getSignerDeposit(tx *solana.Transaction, inst *solana.CompiledInstruction)
return "", err
}

// there should be 3 accounts for a deposit instruction
if len(instructionAccounts) != accountsNumDeposit {
return "", fmt.Errorf("want %d accounts, got %d", accountsNumDeposit, len(instructionAccounts))
// there should be at least all mandatory accounts for a deposit instruction
if len(instructionAccounts) < accountsNumDeposit {
return "", fmt.Errorf("want required %d accounts, got %d", accountsNumDeposit, len(instructionAccounts))
}

// the accounts are [signer, pda, system_program]
Expand All @@ -125,10 +225,10 @@ func getSignerAndSPLFromDepositSPLAccounts(
return "", "", err
}

// there should be 7 accounts for a deposit spl instruction
if len(instructionAccounts) != accountsNumberDepositSPL {
// there should be at least all mandatory accounts for a deposit spl instruction
if len(instructionAccounts) < accountsNumberDepositSPL {
return "", "", fmt.Errorf(
"want %d accounts, got %d",
"want required %d accounts, got %d",
accountsNumberDepositSPL,
len(instructionAccounts),
)
Expand Down
Loading

0 comments on commit 87bf74f

Please sign in to comment.