如何取消Jetton(TON)代币转账交易

问题描述 投票:0回答:1

我在转移 Jetton 代币时遇到交易回滚问题:例如,一个用户想要将 Jetton 代币转移给另一个用户,而该用户的所有者是另一个智能合约。转账分三笔完成:

  1. 第一笔交易是发送到发送者钱包的 tokenTransfer 消息,发送者在钱包中发送必要的数据,例如接收者地址、要转移的代币数量等。
  2. 第二笔交易正在向接收者的钱包发送一条transferInternal消息。
  3. 第三笔交易是向该钱包所有者的地址(接收者的地址)发送一条 tokenNotification 消息。

实际上问题可能是这样的:当合约收到钱包合约发来的 tokenNotification 消息后,由于某种原因想要回滚这笔交易(例如,在负责处理 tokenNotification 消息的代码中,有一些检查尚未通过),那么他将只能取消带有 tokenNotification 消息的交易(即第三笔交易),但令牌传输本身在第二笔交易中。

建议的选项之一是将代币发送回给用户,但这里的问题是,将代币转回是我的合约发起的另一笔交易,这意味着我的合约必须为转账支付佣金。也就是说,用户可以攻击我的合约,这样他就会发送许多我的合约认为无效的交易,并尝试将代币发回,并花费他的 TON 来支付佣金。

这是我的 Jetton 合约代码

@interface("org.ton.jetton.wallet")
contract JettonDefaultWallet {
    const minTonsForStorage: Int = ton("0.019");
    const gasConsumption: Int = ton("0.013");
    balance: Int as coins = 0;
    owner: Address;
    master: Address;

    init(owner: Address, master: Address){
        self.balance = 0;
        self.owner = owner;
        self.master = master;
    }

    receive(msg: TokenTransfer){
        // 0xf8a7ea5
        let ctx: Context = context(); // Check sender
        require(ctx.sender == self.owner, "Invalid sender");
        let final: Int =
            (((ctx.readForwardFee() * 2 + 2 * self.gasConsumption) + self.minTonsForStorage) + msg.forward_ton_amount); // Gas checks, forward_ton = 0.152
        require(ctx.value > final, "Invalid value");
        // Update balance
        self.balance = (self.balance - msg.amount);
        require(self.balance >= 0, "Invalid balance");
        let init: StateInit = initOf JettonDefaultWallet(msg.sender, self.master);
        let wallet_address: Address = contractAddress(init);
        send(SendParameters{
                to: wallet_address,
                value: 0,
                mode: SendRemainingValue,
                bounce: true,
                body: TokenTransferInternal{ // 0x178d4519
                    query_id: msg.query_id,
                    amount: msg.amount,
                    from: self.owner,
                    response_destination: msg.response_destination,
                    forward_ton_amount: msg.forward_ton_amount,
                    forward_payload: msg.forward_payload
                }.toCell(),
                code: init.code,
                data: init.data
            }
        );
    }

    receive(msg: TokenTransferInternal){

        // 0x178d4519
        let ctx: Context = context();                
        if (ctx.sender != self.master) {
            let sinit: StateInit = initOf JettonDefaultWallet(msg.from, self.master);
            require(contractAddress(sinit) == ctx.sender, "Invalid sender!");
        }
        // Update balance
        self.balance = (self.balance + msg.amount);
        require(self.balance >= 0, "Invalid balance");
        // Get value for gas
        let msg_value: Int = self.msg_value(ctx.value);
        let fwd_fee: Int = ctx.readForwardFee();
        if (msg.forward_ton_amount > 0) {
            msg_value = ((msg_value - msg.forward_ton_amount) - fwd_fee);
            send(SendParameters{
                    to: self.owner,
                    value: msg.forward_ton_amount,
                    mode: SendPayGasSeparately,
                    bounce: false,
                    body: TokenNotification{ // 0x7362d09c -- Remind the new Owner
                        query_id: msg.query_id,
                        amount: msg.amount,
                        from: msg.from,
                        forward_payload: msg.forward_payload
                    }.toCell()
                }
            );
        }
        // 0xd53276db -- Cashback to the original Sender
        if (msg.response_destination != null && msg_value > 0) {
            send(SendParameters{
                    to: msg.response_destination!!,
                    value: msg_value,
                    bounce: false,
                    body: TokenExcesses{query_id: msg.query_id}.toCell(),
                    mode: SendPayGasSeparately
                }
            );
        }
    }

    receive(msg: TokenBurn){
        let ctx: Context = context();
        require(ctx.sender == self.owner, "Invalid sender"); // Check sender

        self.balance = (self.balance - msg.amount); // Update balance
        require(self.balance >= 0, "Invalid balance");
        let fwd_fee: Int = ctx.readForwardFee(); // Gas checks
        require(ctx.value > ((fwd_fee + 2 * self.gasConsumption) + self.minTonsForStorage), "Invalid value - Burn");
        // Burn tokens
        send(SendParameters{
                to: self.master,
                value: 0,
                mode: SendRemainingValue,
                bounce: true,
                body: TokenBurnNotification{
                    query_id: msg.query_id,
                    amount: msg.amount,
                    sender: self.owner,
                    response_destination: msg.response_destination
                }.toCell()
            }
        );
    }

    fun msg_value(value: Int): Int {
        let msg_value1: Int = value;
        let ton_balance_before_msg: Int = (myBalance() - msg_value1);
        let storage_fee: Int = (self.minTonsForStorage - min(ton_balance_before_msg, self.minTonsForStorage));
        msg_value1 = (msg_value1 - (storage_fee + self.gasConsumption));
        return msg_value1;
    }

    bounced(msg: bounced<TokenTransferInternal>){
        self.balance = (self.balance + msg.amount);
    }

    bounced(msg: bounced<TokenBurnNotification>){
        self.balance = (self.balance + msg.amount);
    }

    get fun get_wallet_data(): JettonWalletData {
        return
            JettonWalletData{
                balance: self.balance,
                owner: self.owner,
                master: self.master,
                code: initOf JettonDefaultWallet(self.owner, self.master).code
            };
    }
}
token blockchain telegram ton
1个回答
0
投票

您所描述的内容存在一些误解:

当合约[我们称之为R]从钱包合约接收到tokenNotification消息,并且出于某种原因想要回滚该交易时(例如,在负责处理tokenNotification消息的代码中,有一些检查尚未通过),那么他将只能取消带有 tokenNotification 消息的交易(即第三笔交易),但令牌传输本身在第二笔交易中。

按照你的说法,R 即使是第三笔交易也无法回滚。我们画个图吧:

S -[tokenTransfer]-> SW -[transferInternal]-> RW -[tokenNotification]-> R

每条消息都用

-[op_name]->
表示,合约位于两者之间。交易在每个合约上完成,即交易包括:

  • 收到消息
  • 计算结果
  • 动作(即发送消息)。

消息一旦发送,交易就完成了,并且无法回滚。如果

R
已收到来自
tokenNotification
RW
,则
RW
上的交易已完成。

你能做的是

  • 退回消息(例如,
    R
    可以退回
    tokenNotification
    ),这取决于
    RW
    的实现,
    RW
    将如何处理退回的消息。如果它支持回滚整个传奇,它将恢复一些更改并向
    SW
    等发送另一条退回消息,但很可能
    RW
    会忽略它(好吧,我几乎可以肯定 Jettons 就是这种情况,因为我读过他们的一些代码);
  • 将杰顿送回去。

请注意,即使弹跳也需要一些佣金(发送消息需要 Gas),因此任何恢复该链的方法都需要一定数量的 TON。自动执行此类恢复的唯一方法是

  1. 实施支持此类传奇恢复的 Jettons 以及
  2. 使用
    tokenTransfer
    消息发送足够的 TON 以允许恢复过程(理想情况下,在恢复未完成的情况下,您还必须实现从
    R
    发送额外的 TON [不用于恢复] 到
    S
    ).
© www.soinside.com 2019 - 2024. All rights reserved.