Взаимодействие контракт-контракт

Свяжешь два Tact-контракта сообщениями: запрос, ответ, проверка sender и value.

Контракт → контракт

On-chain один контракт не вызывает функцию другого синхронно. Он отправляет internal message; ответ — отдельная транзакция. Согласованность строится на проверке sender() и явных opcode/типах сообщений.


PriceOracle ←→ Consumer

tact (fallback highlight)
// @tact-lang/compiler 1.6.13

message QuoteRequest {
    queryId: Int as uint64;
    replyTo: Address;
}

message QuoteResponse {
    queryId: Int as uint64;
    price: Int as coins;
}

contract PriceOracle(
    admin: Address,
    price: Int as coins,
) {
    receive() { cashback(sender()) }

    receive(msg: QuoteRequest) {
        message(MessageParameters{
            to: msg.replyTo,
            value: 0,
            mode: SendRemainingValue,
            bounce: true,
            body: QuoteResponse{
                queryId: msg.queryId,
                price: self.price,
            }.toCell(),
        });
    }

    receive(msg: QuoteResponse) {
        // Oracle сам ответы не принимает
        throw(403);
    }

    receive("set") {
        require(sender() == self.admin, "not admin");
        // в туториале цена фиксирована через отдельный typed msg в проде
    }

    get fun price(): Int { return self.price }
}

contract Consumer(
    oracle: Address,
    lastPrice: Int as coins,
    lastQueryId: Int as uint64,
) {
    receive() { cashback(sender()) }

    receive("refresh") {
        let q: Int = self.lastQueryId + 1;
        self.lastQueryId = q;
        message(MessageParameters{
            to: self.oracle,
            value: ton("0.05"),
            bounce: true,
            body: QuoteRequest{
                queryId: q,
                replyTo: myAddress(),
            }.toCell(),
        });
    }

    receive(msg: QuoteResponse) {
        require(sender() == self.oracle, "fake oracle");
        require(msg.queryId == self.lastQueryId, "stale query");
        self.lastPrice = msg.price;
        cashback(sender());
    }

    get fun lastPrice(): Int { return self.lastPrice }
}

Как это работает

  • QuoteRequest.replyTo — куда оракул шлёт ответ (часто myAddress()).
  • SendRemainingValue на стороне oracle возвращает остаток value consumer’у вместе с ответом.
  • require(sender() == self.oracle) — без этого любой сможет подделать QuoteResponse.
  • queryId связывает запрос и ответ и отсекает устаревшие реплаи.
  • Ошибки у oracle при bounce: true вернут bounce в consumer — заложили обработчик или миритесь с no-op.

Частые ошибки

Доверяешь полям сообщения, но не sender() → классическая подделка ответа.

Шлёшь value: 0 без mode, который берёт value из входа → сообщение может не оплатить forward fee.

Ждёшь ответ в той же транзакции → его ещё нет; UI/тесты смотрят цепочку транзакций.


Что дальше

Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация