Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions pkg/bindings/trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package bindings

import (
"fmt"

"github.com/smartcontractkit/chainlink-ton/pkg/ccip/bindings/offramp"
"github.com/smartcontractkit/chainlink-ton/pkg/ccip/bindings/router"
"github.com/smartcontractkit/chainlink-ton/pkg/ton/tracetracking"
"github.com/smartcontractkit/chainlink-ton/pkg/ton/tvm"
)

// DefaultTraceStopCondition is the default policy for bounded trace tracking - stopping message (DAG)
// trace tracking in the context of MCMS/CCIP.
//
// Notice: we expect account contracts (e.g., Wallet contracts, MCMS/Timelock, multisigs, etc) to accept
// all incoming messages (replies) and not reject with 0xffff (wrong opcode) but accept/ignore.
//
// - we don't track fwd notifications to uninitialized accounts, bounce = false
// - we don't track outgoing msgs/notifications from third-party CCIP receiver contracts (except for router.CCIPReceiveConfirm)
var DefaultTraceStopCondition tracetracking.StopCondition = func(parent, current *tracetracking.ReceivedMessage) (bool, error) {
ec, err := current.ExitCode()
if err != nil {
return false, fmt.Errorf("failed to get exit code: %w", err)
}

// Stop tracing on NoState exit code (fwd notifications to uninitialized accounts, bounce = false)
if !current.InternalMsg.Bounce && ec == tvm.ExitCodeComputeSkipReasonNoState {
return true, nil // stop tracing
}

opcodeParent, err := tvm.ExtractOpcode(parent.InternalMsg.Body)
if err != nil {
return false, fmt.Errorf("failed to extract opcode from parent message: %w", err)
}

opcodeCurrent, err := tvm.ExtractOpcode(current.InternalMsg.Body)
if err != nil {
return false, fmt.Errorf("failed to extract opcode from current message: %w", err)
}

switch uint64(opcodeParent) {
case offramp.OpcodeCCIPReceive:
// Stop tracing if the current message is not router.CCIPReceiveConfirm (i.e. don't consider
// any other outgoing msgs/notifications from third-party CCIP receiver contracts - on offramp.CCIPReceive)
return uint64(opcodeCurrent) != router.OpcodeCCIPReceiveConfirm, nil
}

return false, nil // Don't stop by default (continue tracing)
}
8 changes: 6 additions & 2 deletions pkg/ccip/bindings/offramp/offramp.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package offramp

import (
"reflect"

"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton"
Expand Down Expand Up @@ -101,8 +103,6 @@ type ConfigInfo struct {

// Methods

const CCIPReceiveOpCode = 0xb3126df1

// CCIPReceive represents the CCIP message received on TON
type CCIPReceive struct {
_ tlb.Magic `tlb:"#b3126df1" json:"-"` //nolint:revive // Ignore opcode tag
Expand Down Expand Up @@ -190,6 +190,10 @@ var TLBs = tvm.MustNewTLBMap([]any{
UpdateDeployables{},
}).MustWithStorageType(Storage{})

var (
OpcodeCCIPReceive = tvm.MustExtractMagic(reflect.TypeOf(CCIPReceive{}))
)

// Config types that implements getter fetching interface with rpc client

// OCR3Base represents the OCR3 base configuration stored on-chain
Expand Down
30 changes: 16 additions & 14 deletions pkg/ccip/bindings/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package router

import (
"math/big"
"reflect"

"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
Expand All @@ -13,15 +14,15 @@ import (
"github.com/smartcontractkit/chainlink-ton/pkg/ton/tvm"
)

const (
OpcodeApplyRampUpdates = 0x7db6745d
OpcodeCCIPSend = 0x31768d95
OpcodeRouteMessage = 0xfc69c50b
OpcodeCCIPReceiveConfirm = 0x1e55bbf6
OpcodeMessageSent = 0x6513f8e1
OpcodeMessageRejected = 0x8ae25114
OpcodeRMNRemoteCurse = 0xf3388046
OpcodeRMNRemoteUncurse = 0x3f153a31
var (
OpcodeApplyRampUpdates = tvm.MustExtractMagic(reflect.TypeOf(ApplyRampUpdates{}))
OpcodeCCIPSend = tvm.MustExtractMagic(reflect.TypeOf(CCIPSend{}))
OpcodeRouteMessage = tvm.MustExtractMagic(reflect.TypeOf(RouteMessage{}))
OpcodeCCIPReceiveConfirm = tvm.MustExtractMagic(reflect.TypeOf(CCIPReceiveConfirm{}))
OpcodeMessageSent = tvm.MustExtractMagic(reflect.TypeOf(MessageSent{}))
OpcodeMessageRejected = tvm.MustExtractMagic(reflect.TypeOf(MessageRejected{}))
OpcodeRMNRemoteCurse = tvm.MustExtractMagic(reflect.TypeOf(RMNRemoteCurse{}))
OpcodeRMNRemoteUncurse = tvm.MustExtractMagic(reflect.TypeOf(RMNRemoteUncurse{}))
)

const (
Expand Down Expand Up @@ -172,6 +173,11 @@ type RMNRemoteUncurse struct {
Subjects common.SnakedCell[Subject] `tlb:"^"`
}

type RMNOwnableMessage[T ownable2step.InMessage] struct {
_ tlb.Magic `tlb:"#af7a9ac6"` //nolint:revive // Ignore opcode tag
Content T `tlb:"."`
}

var TLBs = tvm.MustNewTLBMap([]any{
ApplyRampUpdates{},
CCIPSend{},
Expand All @@ -183,9 +189,5 @@ var TLBs = tvm.MustNewTLBMap([]any{
MessageRejected{},
RMNRemoteCurse{},
RMNRemoteUncurse{},
RMNOwnableMessage[ownable2step.TransferOwnership]{Content: ownable2step.TransferOwnership{}},
}).MustWithStorageType(Storage{})

type RMNOwnableMessage[T ownable2step.InMessage] struct {
_ tlb.Magic `tlb:"#af7a9ac6"` //nolint:revive // Ignore opcode tag
Content T `tlb:"."`
}
62 changes: 55 additions & 7 deletions pkg/ton/tracetracking/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,29 @@ func (m *ReceivedMessage) WaitForTrace(ctx context.Context, c ton.APIClientWrapp
return nil
}

// StopCondition is a function type that defines a condition to determine when to stop
// traversing the message trace. It takes a parent and current ReceivedMessage as input
// and returns a boolean indicating whether the stop condition has been met. This
// is used in functions like TraceExitCodeWith and TraceSucceededWith to limit the scope
// of the trace analysis based on custom criteria.
type StopCondition func(parent, current *ReceivedMessage) (bool, error)

// NoBound is a default StopCondition that never returns true, meaning that trace
// analysis will continue through the entire message trace.
var NoBound StopCondition = func(_, _ *ReceivedMessage) (bool, error) { return false, nil } // Don't stop, no bound condition

// TraceExitCode returns the first non-success exit code found in this message
// or any of its outgoing internal messages. If all messages succeeded, it returns
// the success exit code.
func (m *ReceivedMessage) TraceExitCode() (tvm.ExitCode, error) {
return m.TraceExitCodeWith(NoBound)
}

// TraceExitCodeWith returns the first non-success exit code found in this message
// or any of its outgoing internal messages, stopping the search when the provided
// stop condition is met. If all messages within the boundary succeeded,
// it returns the success exit code.
func (m *ReceivedMessage) TraceExitCodeWith(boundary StopCondition) (tvm.ExitCode, error) {
if m == nil {
return 0, errors.New("cannot get trace exit code from nil ReceivedMessage")
}
Expand All @@ -484,7 +503,17 @@ func (m *ReceivedMessage) TraceExitCode() (tvm.ExitCode, error) {
}

for i := len(curr.OutgoingInternalReceivedMessages) - 1; i >= 0; i-- {
stack = append(stack, curr.OutgoingInternalReceivedMessages[i])
msg := curr.OutgoingInternalReceivedMessages[i]

stop, err := boundary(curr, msg)
if err != nil {
return 0, fmt.Errorf("failed to evaluate stop condition: %w", err)
}
if stop {
continue // Skip messages after the trace stop condition is met
}

stack = append(stack, msg)
}
}

Expand Down Expand Up @@ -526,16 +555,35 @@ func (m *ReceivedMessage) ExitCode() (tvm.ExitCode, error) {
return tvm.ExitCode(computePhase.Details.ExitCode), nil
}

// TraceSucceeded recursively checks if this message
// and all its OutgoingInternalMessagesReceived succeeded.
// TraceSucceeded recursively checks if this message and all its OutgoingInternalMessagesReceived succeeded.
func (m *ReceivedMessage) TraceSucceeded() bool {
succeeded, _ := m.TraceSucceededWith(NoBound) // ok to ignore error
return succeeded
}

// TraceSucceededWith recursively checks if this message and all its OutgoingInternalMessagesReceived succeeded,
// stopping the check when the provided boundary condition is met.
func (m *ReceivedMessage) TraceSucceededWith(boundary StopCondition) (bool, error) {
if !m.Succeeded() {
return false
return false, nil
}

for _, msg := range m.OutgoingInternalReceivedMessages {
if !msg.TraceSucceeded() {
return false
stop, err := boundary(m, msg)
if err != nil {
return false, fmt.Errorf("failed to evaluate stop condition: %w", err)
}
if stop {
continue // Skip messages after the trace bound condition met
}

succeeded, err := msg.TraceSucceededWith(boundary)
if err != nil {
return false, fmt.Errorf("failed to check if trace succeeded: %w", err)
}
if !succeeded {
return false, nil
}
}
return true
return true, nil
}
12 changes: 9 additions & 3 deletions pkg/ton/tracetracking/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
"github.com/smartcontractkit/chainlink-ton/pkg/ton/tvm"
)

// WaitForTrace waits for the trace of a given transaction.
func WaitForTrace(ctx context.Context, c ton.APIClientWrapped, tx *tlb.Transaction) error {
// WaitForTrace waits for the trace of a given transaction and checks if the trace succeeded, stopping the trace
// check when the provided boundary condition is met. If no boundary condition is provided, it checks the entire trace.
func WaitForTrace(ctx context.Context, c ton.APIClientWrapped, tx *tlb.Transaction, boundary ...StopCondition) error {
r, err := MapToReceivedMessage(tx)
if err != nil {
return fmt.Errorf("failed to map tx to ReceivedMessage: %w", err)
Expand All @@ -21,7 +22,12 @@ func WaitForTrace(ctx context.Context, c ton.APIClientWrapped, tx *tlb.Transacti
return fmt.Errorf("failed to wait for trace: %w", err)
}

ec, err := r.TraceExitCode()
boundaryFunc := NoBound // default to no boundary (check entire trace)
if len(boundary) > 0 {
boundaryFunc = boundary[0]
}

ec, err := r.TraceExitCodeWith(boundaryFunc)
if err != nil {
return fmt.Errorf("failed to get outcome exit code: %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/ton/tvm/magic.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ func ExtractMagic(rt reflect.Type) (uint64, error) {
return magic, nil
}

func MustExtractMagic(rt reflect.Type) uint64 {
magic, err := ExtractMagic(rt)
if err != nil {
panic(fmt.Sprintf("failed to extract magic from type %s: %v", rt.Name(), err))
}
return magic
}

// Notice: vendoring github:xssnick/tonutils-go tlb package
func LoadMagic(tag string) (uint64, error) {
tag = strings.TrimSpace(tag)
Expand Down
Loading