Skip to content
4 changes: 2 additions & 2 deletions pkg/ccip/bindings/ocr/executereport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<vec<u8>> - each token data as separate cell ref
// Proofs: vec<bytes32> - 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<vec<u8>>
Proofs common.SnakeData[common.Proof] `tlb:"^"` // vec<bytes32> - inline 256-bit proofs
ProofFlagBits *big.Int `tlb:"## 256"`
Expand Down
4 changes: 2 additions & 2 deletions pkg/ccip/bindings/ocr/executereport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
}
30 changes: 14 additions & 16 deletions pkg/ccip/bindings/offramp/offramp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down
195 changes: 97 additions & 98 deletions pkg/ccip/codec/executecodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's pending to add the gas limit? @huangzhen1997 @vicentevieytes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were commented out because we skipped gasLimit from the contract. But Vicente is adding them back on-chain, so we are going to uncomment this soon in another PR

/*
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
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/ccip/codec/executecodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pkg/ccip/ocr/contract_transmitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading