Skip to content

Commit

Permalink
Merge pull request #471 from tonkeeper/intentions
Browse files Browse the repository at this point in the history
Extract intentions from external messages
  • Loading branch information
erokhinav authored Sep 18, 2024
2 parents aed2ca7 + e5184a8 commit 35587c0
Show file tree
Hide file tree
Showing 45 changed files with 447 additions and 19 deletions.
2 changes: 1 addition & 1 deletion pkg/api/emulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func emulatedTreeToTrace(
transaction, err := core.ConvertTransaction(int32(a.AddrStd.WorkchainId), tongo.Transaction{
Transaction: tree.TX,
BlockID: tongo.BlockIDExt{BlockID: tongo.BlockID{Workchain: int32(a.AddrStd.WorkchainId)}},
})
}, nil)
filteredMsgs := make([]core.Message, 0, len(transaction.OutMsgs))
for _, msg := range transaction.OutMsgs {
if msg.Destination == nil {
Expand Down
9 changes: 6 additions & 3 deletions pkg/api/event_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@ func (h *Handler) GetEvent(ctx context.Context, params oas.GetEventParams) (*oas
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
}
result, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage))
actions, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage))
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
}
result := bath.EnrichWithIntentions(trace, actions)
event, err := h.toEvent(ctx, trace, result, params.AcceptLanguage)
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
Expand Down Expand Up @@ -234,12 +235,13 @@ func (h *Handler) GetAccountEvents(ctx context.Context, params oas.GetAccountEve
skippedInProgress = append(skippedInProgress, traceID.Hash)
continue
}
result, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage))
actions, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage))
if err != nil {
events = append(events, h.toUnknownAccountEvent(account.ID, traceID))
continue
//return nil, toError(http.StatusInternalServerError, err)
}
result := bath.EnrichWithIntentions(trace, actions)
e, err := h.toAccountEvent(ctx, account.ID, trace, result, params.AcceptLanguage, params.SubjectOnly.Value)
if err != nil {
events = append(events, h.toUnknownAccountEvent(account.ID, traceID))
Expand All @@ -262,10 +264,11 @@ func (h *Handler) GetAccountEvents(ctx context.Context, params oas.GetAccountEve
continue
}
i++
result, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage))
actions, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage))
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
}
result := bath.EnrichWithIntentions(trace, actions)
event, err := h.toAccountEvent(ctx, account.ID, trace, result, params.AcceptLanguage, params.SubjectOnly.Value)
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
Expand Down
1 change: 1 addition & 0 deletions pkg/bath/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type (
InscriptionTransfer *InscriptionTransferAction `json:",omitempty"`
Success bool
Type ActionType
Error *string
BaseTransactions []ton.Bits256
}
TonTransferAction struct {
Expand Down
18 changes: 18 additions & 0 deletions pkg/bath/bath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ func TestFindActions(t *testing.T) {
tongo.MustParseBlockID("(0,8000000000000000,34021598)"),
// nft transfer
tongo.MustParseBlockID("(0,8000000000000000,33600829)"),
// failed dedust swap
tongo.MustParseBlockID("(0,7000000000000000,45592983)"),
}),
)

Expand Down Expand Up @@ -394,6 +396,21 @@ func TestFindActions(t *testing.T) {
hash: "e27cdf1d6987a3e74dc8d9c4a52a5b22112fe3946d0dceadf8160b74f80b9d46",
filenamePrefix: "governance_jetton_mint",
},
{
name: "failed simple transfer",
hash: "63d358331c0154ade48ab92b4634c3fff004f42ce7201a37973938862d232c0f",
filenamePrefix: "failed-simple-transfer",
},
{
name: "simple transfers, one of two failed",
hash: "ac0b8bf04949cb72759832ec6c123b3677b8ca140899ac859aa66a558e4f4c11",
filenamePrefix: "simple-transfers-one-failed",
},
{
name: "failed dedust swap",
hash: "887c7763f41ca4a4b9de28900ab514caabc0c27ed5b41d9918d60f5e7f4a9d96",
filenamePrefix: "failed-dedust-swap",
},
} {
t.Run(c.name, func(t *testing.T) {
trace, err := storage.GetTrace(context.Background(), tongo.MustParseHash(c.hash))
Expand All @@ -411,6 +428,7 @@ func TestFindActions(t *testing.T) {
WithStraws(straws),
WithInformationSource(source))
require.Nil(t, err)
actionsList = EnrichWithIntentions(trace, actionsList)
results := result{
Actions: actionsList.Actions,
}
Expand Down
227 changes: 227 additions & 0 deletions pkg/bath/intentions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package bath

import (
"github.com/tonkeeper/opentonapi/internal/g"
"github.com/tonkeeper/opentonapi/pkg/core"
"github.com/tonkeeper/tongo/abi"
"github.com/tonkeeper/tongo/boc"
"golang.org/x/exp/slices"
"reflect"
)

type OutMessage struct {
body any
mode uint8
messageRelaxed abi.MessageRelaxed
tx *core.Transaction
}

func EnrichWithIntentions(trace *core.Trace, actions *ActionsList) *ActionsList {
outMessages, inMsgCount := extractIntentions(trace)
if len(outMessages) <= inMsgCount {
return actions
}
outMessages = removeMatchedIntentions(trace, &outMessages)
for _, outMsg := range outMessages {
newAction := createActionFromMessage(outMsg)
added := false
for i, action := range actions.Actions {
if slices.Contains(action.BaseTransactions, outMsg.tx.Hash) {
actions.Actions = slices.Insert(actions.Actions, i+1, newAction)
added = true
break
}
}
if !added {
actions.Actions = append(actions.Actions, newAction)
}
}
return actions
}

func extractIntentions(trace *core.Trace) ([]OutMessage, int) {
var outMessages []OutMessage
var inMsgCount int

var getIntentions func(*core.Trace)
getIntentions = func(trace *core.Trace) {
outMessages = append(outMessages, getOutMessages(&trace.Transaction)...)
for _, child := range trace.Children {
if child.InMsg != nil {
inMsgCount += 1
}
getIntentions(child)
}
}
getIntentions(trace)

return outMessages, inMsgCount
}

func removeMatchedIntentions(trace *core.Trace, intentions *[]OutMessage) []OutMessage {
var matchAndRemove func(*core.Trace)
matchAndRemove = func(trace *core.Trace) {
for _, child := range trace.Children {
for i, outMsg := range *intentions {
if isMatch(outMsg, child.Transaction.InMsg) {
// remove this outgoing message
*intentions = append((*intentions)[:i], (*intentions)[i+1:]...)
}
}
matchAndRemove(child)
}
}
matchAndRemove(trace)
return *intentions
}

func isMatch(msgOut OutMessage, msgIn *core.Message) bool {
if msgIn == nil {
return false
}

if !compareMessageFields(msgOut, msgIn) {
return false
}

_, ok := msgOut.body.(*boc.Cell)
if (msgOut.body == nil || ok) && msgIn.DecodedBody == nil {
return true
}

if msgOut.body == nil || msgIn.DecodedBody == nil {
return false
}

if reflect.TypeOf(msgOut.body) != reflect.TypeOf(msgIn.DecodedBody.Value) {
return false
}

// compare message body
switch bodyOut := msgOut.body.(type) {
case abi.TextCommentMsgBody:
bodyIn := msgIn.DecodedBody.Value.(abi.TextCommentMsgBody)
return bodyOut.Text == bodyIn.Text
case abi.JettonTransferMsgBody:
bodyIn := msgIn.DecodedBody.Value.(abi.JettonTransferMsgBody)
return bodyIn.QueryId == bodyOut.QueryId
case abi.NftTransferMsgBody:
bodyIn := msgIn.DecodedBody.Value.(abi.NftTransferMsgBody)
return bodyIn.QueryId == bodyOut.QueryId
case abi.DedustSwapMsgBody:
bodyIn := msgIn.DecodedBody.Value.(abi.DedustSwapMsgBody)
return bodyIn.QueryId == bodyOut.QueryId
default:
return true // not supported yet, so removed
}
}

func compareMessageFields(msgOut OutMessage, msgIn *core.Message) bool {
msg := msgOut.messageRelaxed.MessageInternal

if msg.Dest != msgIn.Destination.ToMsgAddress() {
return false
}

if msgOut.mode < 128 && int64(msg.Value.Grams) != msgIn.Value {
return false
}

return true
}

func getOutMessages(transaction *core.Transaction) []OutMessage {
if transaction.InMsg.DecodedBody == nil {
return []OutMessage{}
}

var messages []OutMessage
switch v := transaction.InMsg.DecodedBody.Value.(type) {
case abi.WalletSignedV3ExtInMsgBody:
for _, msg := range v.Payload {
messages = append(messages, OutMessage{
body: msg.Message.MessageInternal.Body.Value.Value,
mode: msg.Mode,
tx: transaction,
messageRelaxed: msg.Message})
}
case abi.WalletSignedV4ExtInMsgBody:
for _, msg := range v.Payload {
messages = append(messages, OutMessage{
body: msg.Message.MessageInternal.Body.Value.Value,
mode: msg.Mode,
tx: transaction,
messageRelaxed: msg.Message})
}
case abi.WalletSignedExternalV5R1ExtInMsgBody:
if v.Actions != nil {
for _, msg := range *v.Actions {
messages = append(messages, OutMessage{
body: msg.Msg.MessageInternal.Body.Value.Value,
mode: msg.Mode,
tx: transaction,
messageRelaxed: msg.Msg})
}
}
case abi.WalletSignedInternalV5R1MsgBody:
if v.Actions != nil {
for _, msg := range *v.Actions {
messages = append(messages, OutMessage{
body: msg.Msg.MessageInternal.Body.Value.Value,
mode: msg.Mode,
tx: transaction,
messageRelaxed: msg.Msg})
}
}
case abi.HighloadWalletSignedV3ExtInMsgBody:
messages = []OutMessage{{
body: v.Msg.MessageToSend.MessageInternal.Body.Value.Value,
mode: v.Msg.SendMode,
tx: transaction,
messageRelaxed: v.Msg.MessageToSend}}
}
return messages
}

func createActionFromMessage(msgOut OutMessage) Action {
var action Action
switch body := msgOut.body.(type) {
case abi.TextCommentMsgBody:
action = Action{Type: TonTransfer, TonTransfer: &TonTransferAction{
Recipient: parseAccount(msgOut.messageRelaxed.MessageInternal.Dest).Address,
Sender: msgOut.tx.Account,
Comment: g.Pointer(string(body.Text))}}
if msgOut.mode < 128 {
action.TonTransfer.Amount = int64(msgOut.messageRelaxed.MessageInternal.Value.Grams)
if msgOut.tx.EndBalance < action.TonTransfer.Amount {
action.Error = g.Pointer("Not enough balance")
}
}
case abi.NftTransferMsgBody:
action = Action{Type: NftItemTransfer, NftItemTransfer: &NftTransferAction{
Recipient: &parseAccount(body.NewOwner).Address,
Sender: &msgOut.tx.Account,
Nft: parseAccount(msgOut.messageRelaxed.MessageInternal.Dest).Address,
}}
case abi.JettonTransferMsgBody:
action = Action{Type: JettonTransfer, JettonTransfer: &JettonTransferAction{
Recipient: &parseAccount(body.Destination).Address,
Sender: &msgOut.tx.Account,
Amount: body.Amount,
SendersWallet: parseAccount(msgOut.messageRelaxed.MessageInternal.Dest).Address,
}}
default:
action = Action{Type: TonTransfer, TonTransfer: &TonTransferAction{
Recipient: parseAccount(msgOut.messageRelaxed.MessageInternal.Dest).Address,
Sender: msgOut.tx.Account}}
if msgOut.mode < 128 {
action.TonTransfer.Amount = int64(msgOut.messageRelaxed.MessageInternal.Value.Grams)
if msgOut.tx.EndBalance < action.TonTransfer.Amount {
action.Error = g.Pointer("Not enough balance")
}
}
}

action.Success = false
return action
}
3 changes: 3 additions & 0 deletions pkg/bath/testdata/buy-nft-on-fragment.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"Success": true,
"Type": "NftPurchase",
"Error": null,
"BaseTransactions": [
"268f46a593b64400a93eb17a0945629647fdd662fb00ff69b58327d10336022b",
"29c039520eeef18295d428e919b7b33bd472f50ab3e9713b767a4f5e5c55181e"
Expand All @@ -26,6 +27,7 @@
},
"Success": true,
"Type": "TonTransfer",
"Error": null,
"BaseTransactions": [
"9fc8546b6f866685908c18b91e14d0fbafb39d6389403831b3ba63c3e60399e5"
]
Expand All @@ -41,6 +43,7 @@
},
"Success": true,
"Type": "TonTransfer",
"Error": null,
"BaseTransactions": [
"fa4c9108bebce63aff79570fceccf99126996b1e8793fcbdcd56a0b8752a2ee1"
]
Expand Down
1 change: 1 addition & 0 deletions pkg/bath/testdata/cut-jetton-transfer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"Success": true,
"Type": "JettonTransfer",
"Error": null,
"BaseTransactions": [
"ae17905025e6920b8ca4f036c79bf3cdddeea052556f9d2b21c574670caf1d12",
"9187161025b3f6249b5d57b8a347bb6e7f82c364fb7f0efb3261f54604574e50",
Expand Down
1 change: 1 addition & 0 deletions pkg/bath/testdata/dedust-swap-from-ton.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"Success": true,
"Type": "JettonSwap",
"Error": null,
"BaseTransactions": [
"c85ebd3043603612af808ab874ac1d19d1a68725afc3c4a78589ed5e88372b21",
"bdef4c941f1f311c7de9badebc71464752c6599ae7cdcf514cd449726d750dd6",
Expand Down
2 changes: 2 additions & 0 deletions pkg/bath/testdata/dedust-swap-jettons.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"Success": true,
"Type": "JettonSwap",
"Error": null,
"BaseTransactions": [
"c18eaf143061a879bdd81f4db08cf4e37ebaeeea03b350fcf33dce1fa486bb62",
"2e2cdf4736ba039f1e86faf195c6ac68835c030e150cbabcf96ce20514fbf7e4",
Expand All @@ -38,6 +39,7 @@
},
"Success": true,
"Type": "ContractDeploy",
"Error": null,
"BaseTransactions": [
"2e2cdf4736ba039f1e86faf195c6ac68835c030e150cbabcf96ce20514fbf7e4"
]
Expand Down
1 change: 1 addition & 0 deletions pkg/bath/testdata/dedust-swap-to-ton.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"Success": true,
"Type": "JettonSwap",
"Error": null,
"BaseTransactions": [
"bba7985c6b547c2bce3a2979b41bf5b0c5bf5a399bcbee69a80572039147ce4d",
"93a65eccc2922a64274a090e52973ff9ddd26b86d6458ab2bed36ed3c051ec59",
Expand Down
Loading

0 comments on commit 35587c0

Please sign in to comment.