All Downloads are FREE. Search and download functionalities are using the official Maven repository.

contracts.wallets.highload-wallet-v3.func Maven / Gradle / Ivy

{ - TL-B scheme
storage$_ public_key:bits256
          subwallet_id:uint32
          old_queries:(HashmapE 14 ^Cell)
          queries:(HashmapE 14 ^Cell)
          last_clean_time:uint64
          timeout:uint22
          = Storage;

_ shift:uint13 bit_number:(## 10) { bit_number >= 0 } { bit_number < 1023 } = QueryId;

// highload v3

// crc32('internal_transfer n:# query_id:uint64 actions:^OutList n = InternalMsgBody n') = ae42e5a4

internal_transfer#ae42e5a4 {n:#} query_id:uint64 actions:^(OutList n) = InternalMsgBody n;

_ {n:#}  subwallet_id:uint32 message_to_send:^Cell send_mode:uint8 query_id:QueryId created_at:uint64 timeout:uint22 = MsgInner;

msg_body$_ {n:#} signature:bits512 ^(MsgInner) = ExternalInMsgBody;

-}

#include "imports/stdlib.fc";

;;; Store binary true b{1} into `builder` [b]
builder store_true(builder b) asm "STONE";
;;; Stores [x] binary zeroes into `builder` [b].
builder store_zeroes(builder b, int x) asm "STZEROES";
;;; Store `cell` [actions] to register c5 (out actions)
() set_actions(cell actions) impure asm "c5 POP";

const int op::internal_transfer = 0xae42e5a4;

const int error::invalid_signature = 33;
const int error::invalid_subwallet_id = 34;
const int error::invalid_created_at = 35;
const int error::already_executed = 36;
const int error::invalid_message_to_send = 37;
const int error::invalid_timeout = 38;

const int KEY_SIZE = 13;
const int SIGNATURE_SIZE = 512;
const int PUBLIC_KEY_SIZE = 256;
const int SUBWALLET_ID_SIZE = 32;
const int TIMESTAMP_SIZE = 64;
const int TIMEOUT_SIZE = 22; ;; 2^22 / 60 / 60 / 24 - up to ~48 days

const int CELL_BITS_SIZE = 1023;
const int BIT_NUMBER_SIZE = 10; ;; 2^10 = 1024

() recv_internal(cell in_msg_full, slice in_msg_body) impure {
    (int body_bits, int body_refs) = in_msg_body.slice_bits_refs();
    ifnot ((body_refs == 1) & (body_bits == MSG_OP_SIZE + MSG_QUERY_ID_SIZE)) {
        return (); ;; just accept TONs
    }

    slice in_msg_full_slice = in_msg_full.begin_parse();
    int msg_flags = in_msg_full_slice~load_msg_flags();
    if (is_bounced(msg_flags)) {
        return ();
    }

    slice sender_address = in_msg_full_slice~load_msg_addr();

    ;; not from myself
    if (~ sender_address.equal_slices_bits(my_address())) {
        return ();  ;; just accept TONs
    }

    int op = in_msg_body~load_op();

    if (op == op::internal_transfer) {
        in_msg_body~skip_query_id();
        cell actions = in_msg_body.preload_ref();
        cell old_code = my_code();
        set_actions(actions);
        set_code(old_code); ;; prevent to change smart contract code
        return ();
    }
}

() recv_external(slice msg_body) impure {
    cell msg_inner = msg_body~load_ref();
    slice signature = msg_body~load_bits(SIGNATURE_SIZE);
    msg_body.end_parse();
    int msg_inner_hash = msg_inner.cell_hash();

    slice data_slice = get_data().begin_parse();
    int public_key = data_slice~load_uint(PUBLIC_KEY_SIZE);
    int subwallet_id = data_slice~load_uint(SUBWALLET_ID_SIZE);
    cell old_queries = data_slice~load_dict();
    cell queries = data_slice~load_dict();
    int last_clean_time = data_slice~load_uint(TIMESTAMP_SIZE);
    int timeout = data_slice~load_uint(TIMEOUT_SIZE);
    data_slice.end_parse();

    if (last_clean_time < (now() - timeout)) {
        (old_queries, queries) = (queries, null());
        if (last_clean_time < (now() - (timeout * 2))) {
            old_queries = null();
        }
        last_clean_time = now();
    }

    throw_unless(error::invalid_signature, check_signature(msg_inner_hash, signature, public_key));

    slice msg_inner_slice = msg_inner.begin_parse();
    int _subwallet_id = msg_inner_slice~load_uint(SUBWALLET_ID_SIZE);
    cell message_to_send = msg_inner_slice~load_ref();
    int send_mode = msg_inner_slice~load_uint(8);
    int shift = msg_inner_slice~load_uint(KEY_SIZE);
    int bit_number = msg_inner_slice~load_uint(BIT_NUMBER_SIZE);
    int created_at = msg_inner_slice~load_uint(TIMESTAMP_SIZE);
    int _timeout  = msg_inner_slice~load_uint(TIMEOUT_SIZE);
    msg_inner_slice.end_parse();

    throw_unless(error::invalid_subwallet_id, _subwallet_id == subwallet_id);
    throw_unless(error::invalid_timeout, _timeout == timeout);

    throw_unless(error::invalid_created_at, created_at > now() - timeout);
    throw_unless(error::invalid_created_at, created_at <= now());

    (cell value, int found) = old_queries.udict_get_ref?(KEY_SIZE, shift);
    if (found) {
        slice value_slice = value.begin_parse();
        value_slice~skip_bits(bit_number);
        throw_if(error::already_executed, value_slice.preload_int(1));
    }

    (cell value, int found) = queries.udict_get_ref?(KEY_SIZE, shift);
    builder new_value = null();
    if (found) {
        slice value_slice = value.begin_parse();
        (slice tail, slice head) = value_slice.load_bits(bit_number);
        throw_if(error::already_executed, tail~load_int(1));
        new_value = begin_cell().store_slice(head).store_true().store_slice(tail);
    } else {
        new_value = begin_cell().store_zeroes(bit_number).store_true().store_zeroes(CELL_BITS_SIZE - bit_number - 1);
    }

    accept_message();

    queries~udict_set_ref(KEY_SIZE, shift, new_value.end_cell());

    set_data(begin_cell()
        .store_uint(public_key, PUBLIC_KEY_SIZE)
        .store_uint(subwallet_id, SUBWALLET_ID_SIZE)
        .store_dict(old_queries)
        .store_dict(queries)
        .store_uint(last_clean_time, TIMESTAMP_SIZE)
        .store_uint(timeout, TIMEOUT_SIZE)
        .end_cell());


    commit();

    ;; after commit, check the message to prevent an error in the action phase

    slice message_slice = message_to_send.begin_parse();
    {-
       https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L123C1-L124C33
       currencies$_ grams:Grams other:ExtraCurrencyCollection = CurrencyCollection;
       extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) = ExtraCurrencyCollection;

       https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L135
       int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
       src:MsgAddress dest:MsgAddressInt
       value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
       created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;

      https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L155
      message$_ {X:Type} info:CommonMsgInfoRelaxed
      init:(Maybe (Either StateInit ^StateInit))
      body:(Either X ^X) = MessageRelaxed X;
    -}

    throw_if(error::invalid_message_to_send, message_slice~load_uint(1)); ;; int_msg_info$0
    int msg_flags = message_slice~load_uint(3); ;; ihr_disabled:Bool bounce:Bool bounced:Bool
    if (is_bounced(msg_flags)) {
        return ();
    }
    slice message_source_adrress = message_slice~load_msg_addr(); ;; src
    throw_unless(error::invalid_message_to_send, is_address_none(message_source_adrress));
    message_slice~load_msg_addr(); ;; dest
    message_slice~load_coins(); ;; value.coins
    message_slice = message_slice.skip_dict(); ;; value.other extra-currencies
    message_slice~load_coins(); ;; ihr_fee
    message_slice~load_coins(); ;; fwd_fee
    message_slice~skip_bits(64 + 32); ;; created_lt:uint64 created_at:uint32
    int maybe_state_init = message_slice~load_uint(1);
    throw_if(error::invalid_message_to_send, maybe_state_init); ;; throw if state-init included (state-init not supported)
    int either_body = message_slice~load_int(1);
    if (either_body) {
        message_slice~load_ref();
        message_slice.end_parse();
    }

    ;; send message with IGNORE_ERRORS flag to ignore errors in the action phase

    send_raw_message(message_to_send, send_mode | SEND_MODE_IGNORE_ERRORS);
}


int get_public_key() method_id {
    return get_data().begin_parse().preload_uint(PUBLIC_KEY_SIZE);
}

int get_subwallet_id() method_id {
    slice data_slice = get_data().begin_parse();
    data_slice~skip_bits(PUBLIC_KEY_SIZE); ;; skip public_key
    return data_slice.preload_uint(SUBWALLET_ID_SIZE);
}

int get_last_clean_time() method_id {
    slice data_slice = get_data().begin_parse();
    data_slice~skip_bits(PUBLIC_KEY_SIZE + SUBWALLET_ID_SIZE + 1 + 1); ;; skip: public_key, subwallet_id, old_queried, queries
    return data_slice.preload_uint(TIMESTAMP_SIZE);
}

int get_timeout() method_id {
    slice data_slice = get_data().begin_parse();
    data_slice~skip_bits(PUBLIC_KEY_SIZE + SUBWALLET_ID_SIZE + 1 + 1 + TIMESTAMP_SIZE); ;; skip: public_key, subwallet_id, old_queried, queries, last_clean_time
    return data_slice.preload_uint(TIMEOUT_SIZE);
}

int processed?(int query_id, int need_clean) method_id {
    int shift = query_id >> BIT_NUMBER_SIZE;
    int bit_number = query_id & CELL_BITS_SIZE;

    slice data_slice = get_data().begin_parse();
    data_slice~skip_bits(PUBLIC_KEY_SIZE + SUBWALLET_ID_SIZE); ;; skip: public_key, subwallet_id
    cell old_queries = data_slice~load_dict();
    cell queries = data_slice~load_dict();
    int last_clean_time = data_slice~load_uint(TIMESTAMP_SIZE);
    int timeout = data_slice~load_uint(TIMEOUT_SIZE);
    data_slice.end_parse();

    if (need_clean) {
        if (last_clean_time < (now() - timeout)) {
            (old_queries, queries) = (queries, null());
            if (last_clean_time < (now() - (timeout * 2))) {
                old_queries = null();
            }
            last_clean_time = now();
        }
    }

    (cell value, int found) = old_queries.udict_get_ref?(KEY_SIZE, shift);
    if (found) {
        slice value_slice = value.begin_parse();
        value_slice~skip_bits(bit_number);
        if (value_slice.preload_int(1)) {
            return TRUE;
        }
    }

    (cell value, int found) = queries.udict_get_ref?(KEY_SIZE, shift);
    if (found) {
        slice value_slice = value.begin_parse();
        value_slice~skip_bits(bit_number);
        if (value_slice.preload_int(1)) {
            return TRUE;
        }
    }

    return FALSE;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy