diff --git a/pkg/ccip/bindings/ocr/executereport.go b/pkg/ccip/bindings/ocr/executereport.go index 07f173c67..17d96ff10 100644 --- a/pkg/ccip/bindings/ocr/executereport.go +++ b/pkg/ccip/bindings/ocr/executereport.go @@ -10,12 +10,12 @@ import ( ) // ExecuteReport represents CCIP execute report messages on the TON blockchain. -// Messages: single message as cell reference (TON contract reads first message only) +// Message: single message as cell reference // OffChainTokenData: vec> - each token data as separate cell ref // Proofs: vec - inline 256-bit proofs using SnakeData with Proof wrapper (matches TypeScript asSnakeData) type ExecuteReport struct { SourceChainSelector uint64 `tlb:"## 64"` - Messages Any2TVMRampMessage `tlb:"^"` // val message = Any2TVMRampMessage.fromCell(report.messages); + Message Any2TVMRampMessage `tlb:"^"` // val message = Any2TVMRampMessage.fromCell(report.messages); OffChainTokenData common.SnakeRef[common.SnakeBytes] `tlb:"^"` // vec> Proofs common.SnakeData[common.Proof] `tlb:"^"` // vec - inline 256-bit proofs ProofFlagBits *big.Int `tlb:"## 256"` diff --git a/pkg/ccip/bindings/ocr/executereport_test.go b/pkg/ccip/bindings/ocr/executereport_test.go index 078abaf1f..b4ada7bca 100644 --- a/pkg/ccip/bindings/ocr/executereport_test.go +++ b/pkg/ccip/bindings/ocr/executereport_test.go @@ -116,7 +116,7 @@ func TestExecute_EncodingAndDecoding(t *testing.T) { report := ExecuteReport{ SourceChainSelector: 1, - Messages: rampMessageSlice, + Message: rampMessageSlice, OffChainTokenData: common.SnakeRef[common.SnakeBytes]{make([]byte, 120), make([]byte, 130)}, Proofs: common.SnakeData[common.Proof]{{Value: big.NewInt(0)}, {Value: big.NewInt(0)}}, ProofFlagBits: big.NewInt(0), @@ -135,6 +135,6 @@ func TestExecute_EncodingAndDecoding(t *testing.T) { err = tlb.LoadFromCell(&decoded, newCell.BeginParse()) require.NoError(t, err) require.Equal(t, c.Hash(), newCell.Hash()) - require.Len(t, decoded.Messages.TokenAmounts, 3) + require.Len(t, decoded.Message.TokenAmounts, 3) require.Len(t, decoded.Proofs, 2) } diff --git a/pkg/ccip/bindings/offramp/offramp_test.go b/pkg/ccip/bindings/offramp/offramp_test.go index 8f799bec7..fdd9031e5 100644 --- a/pkg/ccip/bindings/offramp/offramp_test.go +++ b/pkg/ccip/bindings/offramp/offramp_test.go @@ -123,26 +123,24 @@ func TestExecute_EncodingAndDecoding(t *testing.T) { }, } - rampMessageSlice := []ocr.Any2TVMRampMessage{ - { - Header: ocr.RampMessageHeader{ - MessageID: make([]byte, 32), - SourceChainSelector: 1, - DestChainSelector: 2, - SequenceNumber: 1, - Nonce: 0, - }, - Sender: onrampAddr, - Data: make([]byte, 1000), - Receiver: addr, - //GasLimit: tlb.MustFromNano(big.NewInt(1000), 1), - TokenAmounts: tokenAmountsSlice, + rampMessage := ocr.Any2TVMRampMessage{ + Header: ocr.RampMessageHeader{ + MessageID: make([]byte, 32), + SourceChainSelector: 1, + DestChainSelector: 2, + SequenceNumber: 1, + Nonce: 0, }, + Sender: onrampAddr, + Data: make([]byte, 1000), + Receiver: addr, + //GasLimit: tlb.MustFromNano(big.NewInt(1000), 1), + TokenAmounts: tokenAmountsSlice, } report := ocr.ExecuteReport{ SourceChainSelector: 1, - Messages: rampMessageSlice[0], + Message: rampMessage, OffChainTokenData: common.SnakeRef[common.SnakeBytes]{make([]byte, 120), make([]byte, 130)}, Proofs: common.SnakeData[common.Proof]{{Value: big.NewInt(0)}, {Value: big.NewInt(0)}}, ProofFlagBits: big.NewInt(0), @@ -172,7 +170,7 @@ func TestExecute_EncodingAndDecoding(t *testing.T) { err = tlb.LoadFromCell(&decoded, newCell.BeginParse()) require.NoError(t, err) require.Equal(t, c.Hash(), newCell.Hash()) - require.Len(t, decoded.ExecuteReport.Messages.TokenAmounts, 2) + require.Len(t, decoded.ExecuteReport.Message.TokenAmounts, 2) require.Len(t, decoded.ExecuteReport.Proofs, 2) require.Equal(t, execute.QueryID, decoded.QueryID) require.Equal(t, execute.ConfigDigest, decoded.ConfigDigest) diff --git a/pkg/ccip/codec/executecodec.go b/pkg/ccip/codec/executecodec.go index 8be1e0db1..332b7c137 100644 --- a/pkg/ccip/codec/executecodec.go +++ b/pkg/ccip/codec/executecodec.go @@ -51,120 +51,125 @@ func (e *executePluginCodecV1) Encode(ctx context.Context, report ccipocr3.Execu } chainReport := report.ChainReports[0] - var offChainTokenData common.SnakeRef[common.SnakeBytes] - rampMessages := make([]ocr.Any2TVMRampMessage, 0, len(chainReport.Messages)) - - for _, msg := range chainReport.Messages { - tokenAmounts := make([]ocr.Any2TVMTokenTransfer, 0, len(msg.TokenAmounts)) - for _, tokenAmount := range msg.TokenAmounts { - if tokenAmount.Amount.IsEmpty() { - return nil, fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress) - } - - if tokenAmount.Amount.Sign() < 0 { - return nil, fmt.Errorf("negative amount for token: %s", tokenAmount.DestTokenAddress) - } - - if len(tokenAmount.DestTokenAddress) != 36 { - return nil, fmt.Errorf("invalid destTokenAddress address: %v", tokenAmount.DestTokenAddress) - } + // TON supports single chain only, encode single ExecuteReport (not array) + // Contract expects single message: val message = Any2TVMRampMessage.fromCell(report.messages); + if len(chainReport.Messages) == 0 { + return nil, errors.New("no messages to encode") + } - destExecDataDecodedMap, err := e.extraDataCodec.DecodeTokenAmountDestExecData(tokenAmount.DestExecData, chainReport.SourceChainSelector) - if err != nil { - return nil, fmt.Errorf("failed to decode dest exec data: %w", err) - } + if len(chainReport.Messages) > 1 { + return nil, errors.New("TON supports single message only per report") + } - destGasAmount, err := extractDestGasAmountFromMap(destExecDataDecodedMap) - if err != nil { - return nil, fmt.Errorf("extract dest gas amount: %w", err) - } + msg := chainReport.Messages[0] + var offChainTokenData common.SnakeRef[common.SnakeBytes] + var rampMessage ocr.Any2TVMRampMessage + tokenAmounts := make([]ocr.Any2TVMTokenTransfer, 0, len(msg.TokenAmounts)) + for _, tokenAmount := range msg.TokenAmounts { + if tokenAmount.Amount.IsEmpty() { + return nil, fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress) + } - poolAddrCell := common.CrossChainAddress(tokenAmount.SourcePoolAddress) + if tokenAmount.Amount.Sign() < 0 { + return nil, fmt.Errorf("negative amount for token: %s", tokenAmount.DestTokenAddress) + } - extraData, err := tlb.ToCell(common.SnakeBytes(tokenAmount.ExtraData)) - if err != nil { - return nil, fmt.Errorf("pack extra data: %w", err) - } + if len(tokenAmount.DestTokenAddress) != 36 { + return nil, fmt.Errorf("invalid destTokenAddress address: %v", tokenAmount.DestTokenAddress) + } - if len(tokenAmount.DestTokenAddress) < 36 { - return nil, fmt.Errorf("invalid dest token address length: %d", len(tokenAmount.DestTokenAddress)) - } + destExecDataDecodedMap, err := e.extraDataCodec.DecodeTokenAmountDestExecData(tokenAmount.DestExecData, chainReport.SourceChainSelector) + if err != nil { + return nil, fmt.Errorf("failed to decode dest exec data: %w", err) + } - destTokenAddrStr, err := e.addressCodec.AddressBytesToString(tokenAmount.DestTokenAddress) - if err != nil { - return nil, err - } + destGasAmount, err := extractDestGasAmountFromMap(destExecDataDecodedMap) + if err != nil { + return nil, fmt.Errorf("extract dest gas amount: %w", err) + } - DestPoolTonAddr, err := address.ParseAddr(destTokenAddrStr) - if err != nil { - return nil, fmt.Errorf("invalid dest token address %s: %w", destTokenAddrStr, err) - } + poolAddrCell := common.CrossChainAddress(tokenAmount.SourcePoolAddress) - tokenAmounts = append(tokenAmounts, ocr.Any2TVMTokenTransfer{ - SourcePoolAddress: poolAddrCell, - ExtraData: extraData, - DestPoolAddress: DestPoolTonAddr, - Amount: tokenAmount.Amount.Int, - DestGasAmount: destGasAmount, - }) + extraData, err := tlb.ToCell(common.SnakeBytes(tokenAmount.ExtraData)) + if err != nil { + return nil, fmt.Errorf("pack extra data: %w", err) } - header := ocr.RampMessageHeader{ - MessageID: msg.Header.MessageID[:], - SourceChainSelector: uint64(msg.Header.SourceChainSelector), - DestChainSelector: uint64(msg.Header.DestChainSelector), - SequenceNumber: uint64(msg.Header.SequenceNumber), - Nonce: msg.Header.Nonce, + if len(tokenAmount.DestTokenAddress) < 36 { + return nil, fmt.Errorf("invalid dest token address length: %d", len(tokenAmount.DestTokenAddress)) } - tonReceiverAddrStr, err := e.addressCodec.AddressBytesToString(msg.Receiver) + destTokenAddrStr, err := e.addressCodec.AddressBytesToString(tokenAmount.DestTokenAddress) if err != nil { - return nil, fmt.Errorf("error convert receiver address: %w", err) + return nil, err } - tonReceiverAddr, err := address.ParseAddr(tonReceiverAddrStr) + DestPoolTonAddr, err := address.ParseAddr(destTokenAddrStr) if err != nil { - return nil, fmt.Errorf("invalid receiver address %s: %w", tonReceiverAddrStr, err) + return nil, fmt.Errorf("invalid dest token address %s: %w", destTokenAddrStr, err) } - // TODO, re-enable when gas limit from extra args is supported - /* - var gasLimitBigInt *big.Int - var extraArgsDecodeMap map[string]any - if len(msg.ExtraArgs) > 0 { - extraArgsDecodeMap, err = e.extraDataCodec.DecodeExtraArgs(msg.ExtraArgs, chainReport.SourceChainSelector) - if err != nil { - return nil, fmt.Errorf("failed to decode extra args: %w", err) - } - - gasLimitBigInt, err = parseExtraArgsMap(extraArgsDecodeMap) - if err != nil { - return nil, fmt.Errorf("parse extra args map to get gas limit: %w", err) - } + tokenAmounts = append(tokenAmounts, ocr.Any2TVMTokenTransfer{ + SourcePoolAddress: poolAddrCell, + ExtraData: extraData, + DestPoolAddress: DestPoolTonAddr, + Amount: tokenAmount.Amount.Int, + DestGasAmount: destGasAmount, + }) + } + + // TODO, re-enable when gas limit from extra args is supported + /* + var gasLimitBigInt *big.Int + var extraArgsDecodeMap map[string]any + if len(msg.ExtraArgs) > 0 { + extraArgsDecodeMap, err = e.extraDataCodec.DecodeExtraArgs(msg.ExtraArgs, chainReport.SourceChainSelector) + if err != nil { + return nil, fmt.Errorf("failed to decode extra args: %w", err) } - // gas limit can be nil, which means no limit - var gasLimit tlb.Coins - if gasLimitBigInt != nil { - gasLimit, err = tlb.FromNano(gasLimitBigInt, 0) - if err != nil { - return nil, fmt.Errorf("convert gas limit to TON cell: %w", err) - } + gasLimitBigInt, err = parseExtraArgsMap(extraArgsDecodeMap) + if err != nil { + return nil, fmt.Errorf("parse extra args map to get gas limit: %w", err) + } + } + + // gas limit can be nil, which means no limit + var gasLimit tlb.Coins + if gasLimitBigInt != nil { + gasLimit, err = tlb.FromNano(gasLimitBigInt, 0) + if err != nil { + return nil, fmt.Errorf("convert gas limit to TON cell: %w", err) } - */ - rampMsg := ocr.Any2TVMRampMessage{ - Header: header, - Sender: common.CrossChainAddress(msg.Sender), - Data: common.SnakeBytes(msg.Data), - Receiver: tonReceiverAddr, - //GasLimit: gasLimit, // TODO double check if this match with on-chain decimal. Note the offramp contract would not use this value base on current design. - TokenAmounts: tokenAmounts, } + */ - rampMessages = append(rampMessages, rampMsg) + tonReceiverAddrStr, err := e.addressCodec.AddressBytesToString(msg.Receiver) + if err != nil { + return nil, fmt.Errorf("error convert receiver address: %w", err) + } + + tonReceiverAddr, err := address.ParseAddr(tonReceiverAddrStr) + if err != nil { + return nil, fmt.Errorf("invalid receiver address %s: %w", tonReceiverAddrStr, err) + } + + rampMessage = ocr.Any2TVMRampMessage{ + Header: ocr.RampMessageHeader{ + MessageID: msg.Header.MessageID[:], + SourceChainSelector: uint64(msg.Header.SourceChainSelector), + DestChainSelector: uint64(msg.Header.DestChainSelector), + SequenceNumber: uint64(msg.Header.SequenceNumber), + Nonce: msg.Header.Nonce, + }, + Sender: common.CrossChainAddress(msg.Sender), + Data: common.SnakeBytes(msg.Data), + Receiver: tonReceiverAddr, + //GasLimit: gasLimit, // TODO double check if this match with on-chain decimal. Note the offramp contract would not use this value base on current design. + TokenAmounts: tokenAmounts, } - if len(chainReport.Messages) > 0 && len(chainReport.OffchainTokenData) > 0 { + if len(chainReport.OffchainTokenData) > 0 { tokenDataSlice := make([]common.SnakeBytes, len(chainReport.OffchainTokenData[0])) for i, data := range chainReport.OffchainTokenData[0] { tokenDataSlice[i] = data @@ -179,16 +184,10 @@ func (e *executePluginCodecV1) Encode(ctx context.Context, report ccipocr3.Execu proofs = append(proofs, p) } - // TON supports single chain only, encode single ExecuteReport (not array) - // Contract expects single message: val message = Any2TVMRampMessage.fromCell(report.messages); - if len(rampMessages) == 0 { - return nil, errors.New("no messages to encode") - } - // Take only the first message (contract only processes one message at a time) executeReport := ocr.ExecuteReport{ SourceChainSelector: uint64(chainReport.SourceChainSelector), - Messages: rampMessages[0], + Message: rampMessage, OffChainTokenData: offChainTokenData, Proofs: proofs, ProofFlagBits: chainReport.ProofFlagBits.Int, @@ -225,9 +224,9 @@ func (e *executePluginCodecV1) Decode(ctx context.Context, data []byte) (ccipocr proofs = append(proofs, ccipocr3.Bytes32(proof.Value.Bytes())) } - // Messages is a single message (not array) - contract only processes one at a time + // Message is a single message (not array) - contract only processes one at a time messages := make([]ccipocr3.Message, 0, 1) - msg := tonReport.Messages + msg := tonReport.Message tokenAmounts := make([]ccipocr3.RampTokenAmount, 0, len(msg.TokenAmounts)) for _, tokenAmount := range msg.TokenAmounts { diff --git a/pkg/ccip/codec/executecodec_test.go b/pkg/ccip/codec/executecodec_test.go index 7de1fa16f..1257c85b8 100644 --- a/pkg/ccip/codec/executecodec_test.go +++ b/pkg/ccip/codec/executecodec_test.go @@ -21,7 +21,7 @@ import ( func randomTONExecuteReport(t *testing.T, sourceChainSelector uint64) ccipocr3.ExecutePluginReport { const numChainReports = 1 // currently TON supports single report only - const msgsPerReport = 2 + const msgsPerReport = 1 const numTokensPerMsg = 2 chainReports := make([]ccipocr3.ExecutePluginReportSingleChain, numChainReports) diff --git a/pkg/ccip/ocr/contract_transmitter.go b/pkg/ccip/ocr/contract_transmitter.go index ef777754a..4c369f08e 100644 --- a/pkg/ccip/ocr/contract_transmitter.go +++ b/pkg/ccip/ocr/contract_transmitter.go @@ -230,6 +230,6 @@ func extractCCIPTxID(reportBytes []byte, seqNr uint64) string { return fmt.Sprintf("seq-%d", seqNr) } - messageIDHex := hex.EncodeToString(executeReport.Messages.Header.MessageID) - return fmt.Sprintf("seq-%d-msg-%s", executeReport.Messages.Header.SequenceNumber, messageIDHex) + messageIDHex := hex.EncodeToString(executeReport.Message.Header.MessageID) + return fmt.Sprintf("seq-%d-msg-%s", executeReport.Message.Header.SequenceNumber, messageIDHex) }